1 /** \file
2 * SPObject implementation.
3 *
4 * Authors:
5 * Lauris Kaplinski <lauris@kaplinski.com>
6 * bulia byak <buliabyak@users.sf.net>
7 * Stephen Silver <sasilver@users.sourceforge.net>
8 * Jon A. Cruz <jon@joncruz.org>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 1999-2008 authors
12 * Copyright (C) 2001-2002 Ximian, Inc.
13 *
14 * Released under GNU GPL, read the file 'COPYING' for more information
15 */
17 /** \class SPObject
18 *
19 * SPObject is an abstract base class of all of the document nodes at the
20 * SVG document level. Each SPObject subclass implements a certain SVG
21 * element node type, or is an abstract base class for different node
22 * types. The SPObject layer is bound to the SPRepr layer, closely
23 * following the SPRepr mutations via callbacks. During creation,
24 * SPObject parses and interprets all textual attributes and CSS style
25 * strings of the SPRepr, and later updates the internal state whenever
26 * it receives a signal about a change. The opposite is not true - there
27 * are methods manipulating SPObjects directly and such changes do not
28 * propagate to the SPRepr layer. This is important for implementation of
29 * the undo stack, animations and other features.
30 *
31 * SPObjects are bound to the higher-level container SPDocument, which
32 * provides document level functionality such as the undo stack,
33 * dictionary and so on. Source: doc/architecture.txt
34 */
36 #include <cstring>
37 #include <string>
39 #include "helper/sp-marshal.h"
40 #include "xml/node-event-vector.h"
41 #include "attributes.h"
42 #include "color-profile-fns.h"
43 #include "document.h"
44 #include "style.h"
45 #include "sp-object-repr.h"
46 #include "sp-root.h"
47 #include "sp-style-elem.h"
48 #include "sp-script.h"
49 #include "streq.h"
50 #include "strneq.h"
51 #include "xml/repr.h"
52 #include "xml/node-fns.h"
53 #include "debug/event-tracker.h"
54 #include "debug/simple-event.h"
55 #include "debug/demangle.h"
56 #include "util/share.h"
57 #include "util/format.h"
58 #include "util/longest-common-suffix.h"
60 using std::memcpy;
61 using std::strchr;
62 using std::strcmp;
63 using std::strlen;
64 using std::strstr;
66 #define noSP_OBJECT_DEBUG_CASCADE
68 #define noSP_OBJECT_DEBUG
70 #ifdef SP_OBJECT_DEBUG
71 # define debug(f, a...) { g_print("%s(%d) %s:", \
72 __FILE__,__LINE__,__FUNCTION__); \
73 g_print(f, ## a); \
74 g_print("\n"); \
75 }
76 #else
77 # define debug(f, a...) /**/
78 #endif
80 guint update_in_progress = 0; // guard against update-during-update
82 Inkscape::XML::NodeEventVector object_event_vector = {
83 SPObject::sp_object_repr_child_added,
84 SPObject::sp_object_repr_child_removed,
85 SPObject::sp_object_repr_attr_changed,
86 SPObject::sp_object_repr_content_changed,
87 SPObject::sp_object_repr_order_changed
88 };
90 // A friend class used to set internal members on SPObject so as to not expose settors in SPObject's public API
91 class SPObjectImpl
92 {
93 public:
95 /**
96 * Null's the id member of an SPObject without attempting to free prior contents.
97 */
98 static void setIdNull( SPObject* obj ) {
99 if (obj) {
100 obj->id = 0;
101 }
102 }
104 /**
105 * Sets the id member of an object, freeing any prior content.
106 */
107 static void setId( SPObject* obj, gchar const* id ) {
108 if (obj && (id != obj->id) ) {
109 if (obj->id) {
110 g_free(obj->id);
111 obj->id = 0;
112 }
113 if (id) {
114 obj->id = g_strdup(id);
115 }
116 }
117 }
118 };
121 GObjectClass * SPObjectClass::static_parent_class = 0;
123 /**
124 * Registers the SPObject class with Gdk and returns its type number.
125 */
126 GType SPObject::sp_object_get_type()
127 {
128 static GType type = 0;
129 if (!type) {
130 GTypeInfo info = {
131 sizeof(SPObjectClass),
132 NULL, NULL,
133 (GClassInitFunc) SPObjectClass::sp_object_class_init,
134 NULL, NULL,
135 sizeof(SPObject),
136 16,
137 (GInstanceInitFunc) sp_object_init,
138 NULL
139 };
140 type = g_type_register_static(G_TYPE_OBJECT, "SPObject", &info, (GTypeFlags)0);
141 }
142 return type;
143 }
145 /**
146 * Initializes the SPObject vtable.
147 */
148 void SPObjectClass::sp_object_class_init(SPObjectClass *klass)
149 {
150 GObjectClass *object_class;
152 object_class = (GObjectClass *) klass;
154 static_parent_class = (GObjectClass *) g_type_class_ref(G_TYPE_OBJECT);
156 object_class->finalize = SPObject::sp_object_finalize;
158 klass->child_added = SPObject::sp_object_child_added;
159 klass->remove_child = SPObject::sp_object_remove_child;
160 klass->order_changed = SPObject::sp_object_order_changed;
162 klass->release = SPObject::sp_object_release;
164 klass->build = SPObject::sp_object_build;
166 klass->set = SPObject::sp_object_private_set;
167 klass->write = SPObject::sp_object_private_write;
168 }
170 /**
171 * Callback to initialize the SPObject object.
172 */
173 void SPObject::sp_object_init(SPObject *object)
174 {
175 debug("id=%x, typename=%s",object, g_type_name_from_instance((GTypeInstance*)object));
177 object->hrefcount = 0;
178 object->_total_hrefcount = 0;
179 object->document = NULL;
180 object->children = object->_last_child = NULL;
181 object->parent = object->next = NULL;
183 //used XML Tree here.
184 Inkscape::XML::Node *repr = object->getRepr();
185 repr = NULL;
186 SPObjectImpl::setIdNull(object);
188 object->_collection_policy = SPObject::COLLECT_WITH_PARENT;
190 new (&object->_release_signal) sigc::signal<void, SPObject *>();
191 new (&object->_modified_signal) sigc::signal<void, SPObject *, unsigned int>();
192 new (&object->_delete_signal) sigc::signal<void, SPObject *>();
193 new (&object->_position_changed_signal) sigc::signal<void, SPObject *>();
194 object->_successor = NULL;
196 // FIXME: now we create style for all objects, but per SVG, only the following can have style attribute:
197 // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline,
198 // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient,
199 // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject
200 object->style = sp_style_new_from_object(object);
202 object->_label = NULL;
203 object->_default_label = NULL;
204 }
206 /**
207 * Callback to destroy all members and connections of object and itself.
208 */
209 void SPObject::sp_object_finalize(GObject *object)
210 {
211 SPObject *spobject = (SPObject *)object;
213 g_free(spobject->_label);
214 g_free(spobject->_default_label);
215 spobject->_label = NULL;
216 spobject->_default_label = NULL;
218 if (spobject->_successor) {
219 sp_object_unref(spobject->_successor, NULL);
220 spobject->_successor = NULL;
221 }
223 if (((GObjectClass *) (SPObjectClass::static_parent_class))->finalize) {
224 (* ((GObjectClass *) (SPObjectClass::static_parent_class))->finalize)(object);
225 }
227 spobject->_release_signal.~signal();
228 spobject->_modified_signal.~signal();
229 spobject->_delete_signal.~signal();
230 spobject->_position_changed_signal.~signal();
231 }
233 namespace {
235 namespace Debug = Inkscape::Debug;
236 namespace Util = Inkscape::Util;
238 typedef Debug::SimpleEvent<Debug::Event::REFCOUNT> BaseRefCountEvent;
240 class RefCountEvent : public BaseRefCountEvent {
241 public:
242 RefCountEvent(SPObject *object, int bias, Util::ptr_shared<char> name)
243 : BaseRefCountEvent(name)
244 {
245 _addProperty("object", Util::format("%p", object));
246 _addProperty("class", Debug::demangle(g_type_name(G_TYPE_FROM_INSTANCE(object))));
247 _addProperty("new-refcount", Util::format("%d", G_OBJECT(object)->ref_count + bias));
248 }
249 };
251 class RefEvent : public RefCountEvent {
252 public:
253 RefEvent(SPObject *object)
254 : RefCountEvent(object, 1, Util::share_static_string("sp-object-ref"))
255 {}
256 };
258 class UnrefEvent : public RefCountEvent {
259 public:
260 UnrefEvent(SPObject *object)
261 : RefCountEvent(object, -1, Util::share_static_string("sp-object-unref"))
262 {}
263 };
265 }
267 gchar const* SPObject::getId() const {
268 return id;
269 }
271 Inkscape::XML::Node * SPObject::getRepr() {
272 return repr;
273 }
275 Inkscape::XML::Node const* SPObject::getRepr() const{
276 return repr;
277 }
280 /**
281 * Increase reference count of object, with possible debugging.
282 *
283 * \param owner If non-NULL, make debug log entry.
284 * \return object, NULL is error.
285 * \pre object points to real object
286 */
287 SPObject *sp_object_ref(SPObject *object, SPObject *owner)
288 {
289 g_return_val_if_fail(object != NULL, NULL);
290 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
291 g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
293 Inkscape::Debug::EventTracker<RefEvent> tracker(object);
294 g_object_ref(G_OBJECT(object));
295 return object;
296 }
298 /**
299 * Decrease reference count of object, with possible debugging and
300 * finalization.
301 *
302 * \param owner If non-NULL, make debug log entry.
303 * \return always NULL
304 * \pre object points to real object
305 */
306 SPObject *sp_object_unref(SPObject *object, SPObject *owner)
307 {
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<UnrefEvent> tracker(object);
313 g_object_unref(G_OBJECT(object));
314 return NULL;
315 }
317 /**
318 * Increase weak refcount.
319 *
320 * Hrefcount is used for weak references, for example, to
321 * determine whether any graphical element references a certain gradient
322 * node.
323 * \param owner Ignored.
324 * \return object, NULL is error
325 * \pre object points to real object
326 */
327 SPObject *sp_object_href(SPObject *object, gpointer /*owner*/)
328 {
329 g_return_val_if_fail(object != NULL, NULL);
330 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
332 object->hrefcount++;
333 object->_updateTotalHRefCount(1);
335 return object;
336 }
338 /**
339 * Decrease weak refcount.
340 *
341 * Hrefcount is used for weak references, for example, to determine whether
342 * any graphical element references a certain gradient node.
343 * \param owner Ignored.
344 * \return always NULL
345 * \pre object points to real object and hrefcount>0
346 */
347 SPObject *sp_object_hunref(SPObject *object, gpointer /*owner*/)
348 {
349 g_return_val_if_fail(object != NULL, NULL);
350 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
351 g_return_val_if_fail(object->hrefcount > 0, NULL);
353 object->hrefcount--;
354 object->_updateTotalHRefCount(-1);
356 return NULL;
357 }
359 /**
360 * Adds increment to _total_hrefcount of object and its parents.
361 */
362 void SPObject::_updateTotalHRefCount(int increment) {
363 SPObject *topmost_collectable = NULL;
364 for ( SPObject *iter = this ; iter ; iter = iter->parent ) {
365 iter->_total_hrefcount += increment;
366 if ( iter->_total_hrefcount < iter->hrefcount ) {
367 g_critical("HRefs overcounted");
368 }
369 if ( iter->_total_hrefcount == 0 &&
370 iter->_collection_policy != COLLECT_WITH_PARENT )
371 {
372 topmost_collectable = iter;
373 }
374 }
375 if (topmost_collectable) {
376 topmost_collectable->requestOrphanCollection();
377 }
378 }
380 /**
381 * True if object is non-NULL and this is some in/direct parent of object.
382 */
383 bool SPObject::isAncestorOf(SPObject const *object) const {
384 g_return_val_if_fail(object != NULL, false);
385 object = object->parent;
386 while (object) {
387 if ( object == this ) {
388 return true;
389 }
390 object = object->parent;
391 }
392 return false;
393 }
395 namespace {
397 bool same_objects(SPObject const &a, SPObject const &b) {
398 return &a == &b;
399 }
401 }
403 /**
404 * Returns youngest object being parent to this and object.
405 */
406 SPObject const *SPObject::nearestCommonAncestor(SPObject const *object) const {
407 g_return_val_if_fail(object != NULL, NULL);
409 using Inkscape::Algorithms::longest_common_suffix;
410 return longest_common_suffix<SPObject::ConstParentIterator>(this, object, NULL, &same_objects);
411 }
413 SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) {
414 SPObject const *result = 0;
415 if ( obj && ancestor ) {
416 if (obj->parent == ancestor) {
417 result = obj;
418 } else {
419 result = AncestorSon(obj->parent, ancestor);
420 }
421 }
422 return result;
423 }
425 /**
426 * Compares height of objects in tree.
427 *
428 * Works for different-parent objects, so long as they have a common ancestor.
429 * \return \verbatim
430 * 0 positions are equivalent
431 * 1 first object's position is greater than the second
432 * -1 first object's position is less than the second \endverbatim
433 */
434 int sp_object_compare_position(SPObject const *first, SPObject const *second)
435 {
436 int result = 0;
437 if (first != second) {
438 SPObject const *ancestor = first->nearestCommonAncestor(second);
439 // Need a common ancestor to be able to compare
440 if ( ancestor ) {
441 // we have an object and its ancestor (should not happen when sorting selection)
442 if (ancestor == first) {
443 result = 1;
444 } else if (ancestor == second) {
445 result = -1;
446 } else {
447 SPObject const *to_first = AncestorSon(first, ancestor);
448 SPObject const *to_second = AncestorSon(second, ancestor);
450 g_assert(to_second->parent == to_first->parent);
452 result = sp_repr_compare_position(to_first->getRepr(), to_second->getRepr());
453 }
454 }
455 }
456 return result;
457 }
460 /**
461 * Append repr as child of this object.
462 * \pre this is not a cloned object
463 */
464 SPObject *SPObject::appendChildRepr(Inkscape::XML::Node *repr) {
465 if ( !cloned ) {
466 getRepr()->appendChild(repr);
467 return document->getObjectByRepr(repr);
468 } else {
469 g_critical("Attempt to append repr as child of cloned object");
470 return NULL;
471 }
472 }
474 void SPObject::setCSS(SPCSSAttr *css, gchar const *attr)
475 {
476 g_assert(this->getRepr() != NULL);
477 sp_repr_css_set(this->getRepr(), css, attr);
478 }
480 void SPObject::changeCSS(SPCSSAttr *css, gchar const *attr)
481 {
482 g_assert(this->getRepr() != NULL);
483 sp_repr_css_change(this->getRepr(), css, attr);
484 }
486 GSList *SPObject::childList(bool add_ref, Action) {
487 GSList *l = NULL;
488 for ( SPObject *child = firstChild() ; child; child = child->getNext() ) {
489 if (add_ref) {
490 g_object_ref (G_OBJECT (child));
491 }
493 l = g_slist_prepend (l, child);
494 }
495 return l;
497 }
499 /** Gets the label property for the object or a default if no label
500 * is defined.
501 */
502 gchar const *SPObject::label() const {
503 return _label;
504 }
506 /** Returns a default label property for the object. */
507 gchar const *SPObject::defaultLabel() const {
508 if (_label) {
509 return _label;
510 } else {
511 if (!_default_label) {
512 if (getId()) {
513 _default_label = g_strdup_printf("#%s", getId());
514 } else {
515 _default_label = g_strdup_printf("<%s>", getRepr()->name());
516 }
517 }
518 return _default_label;
519 }
520 }
522 /** Sets the label property for the object */
523 void SPObject::setLabel(gchar const *label) {
524 getRepr()->setAttribute("inkscape:label", label, false);
525 }
528 /** Queues the object for orphan collection */
529 void SPObject::requestOrphanCollection() {
530 g_return_if_fail(document != NULL);
532 // do not remove style or script elements (Bug #276244)
533 if (SP_IS_STYLE_ELEM(this)) {
534 // leave it
535 } else if (SP_IS_SCRIPT(this)) {
536 // leave it
537 } else if (SP_IS_PAINT_SERVER(this) && static_cast<SPPaintServer*>(this)->isSwatch() ) {
538 // leave it
539 } else if (IS_COLORPROFILE(this)) {
540 // leave it
541 } else {
542 document->queueForOrphanCollection(this);
544 /** \todo
545 * This is a temporary hack added to make fill&stroke rebuild its
546 * gradient list when the defs are vacuumed. gradient-vector.cpp
547 * listens to the modified signal on defs, and now we give it that
548 * signal. Mental says that this should be made automatic by
549 * merging SPObjectGroup with SPObject; SPObjectGroup would issue
550 * this signal automatically. Or maybe just derive SPDefs from
551 * SPObjectGroup?
552 */
554 this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
555 }
556 }
558 void SPObject::_sendDeleteSignalRecursive() {
559 for (SPObject *child = firstChild(); child; child = child->getNext()) {
560 child->_delete_signal.emit(child);
561 child->_sendDeleteSignalRecursive();
562 }
563 }
565 /**
566 * Deletes the object reference, unparenting it from its parent.
567 *
568 * If the \a propagate parameter is set to true, it emits a delete
569 * signal. If the \a propagate_descendants parameter is true, it
570 * recursively sends the delete signal to children.
571 */
572 void SPObject::deleteObject(bool propagate, bool propagate_descendants)
573 {
574 sp_object_ref(this, NULL);
575 if (propagate) {
576 _delete_signal.emit(this);
577 }
578 if (propagate_descendants) {
579 this->_sendDeleteSignalRecursive();
580 }
582 Inkscape::XML::Node *repr = getRepr();
583 if (repr && sp_repr_parent(repr)) {
584 sp_repr_unparent(repr);
585 }
587 if (_successor) {
588 _successor->deleteObject(propagate, propagate_descendants);
589 }
590 sp_object_unref(this, NULL);
591 }
593 /**
594 * Put object into object tree, under parent, and behind prev;
595 * also update object's XML space.
596 */
597 void SPObject::attach(SPObject *object, SPObject *prev)
598 {
599 //g_return_if_fail(parent != NULL);
600 //g_return_if_fail(SP_IS_OBJECT(parent));
601 g_return_if_fail(object != NULL);
602 g_return_if_fail(SP_IS_OBJECT(object));
603 g_return_if_fail(!prev || SP_IS_OBJECT(prev));
604 g_return_if_fail(!prev || prev->parent == this);
605 g_return_if_fail(!object->parent);
607 sp_object_ref(object, this);
608 object->parent = this;
609 this->_updateTotalHRefCount(object->_total_hrefcount);
611 SPObject *next;
612 if (prev) {
613 next = prev->next;
614 prev->next = object;
615 } else {
616 next = this->children;
617 this->children = object;
618 }
619 object->next = next;
620 if (!next) {
621 this->_last_child = object;
622 }
623 if (!object->xml_space.set)
624 object->xml_space.value = this->xml_space.value;
625 }
627 /**
628 * In list of object's siblings, move object behind prev.
629 */
630 void SPObject::reorder(SPObject *prev) {
631 //g_return_if_fail(object != NULL);
632 //g_return_if_fail(SP_IS_OBJECT(object));
633 g_return_if_fail(this->parent != NULL);
634 g_return_if_fail(this != prev);
635 g_return_if_fail(!prev || SP_IS_OBJECT(prev));
636 g_return_if_fail(!prev || prev->parent == this->parent);
638 SPObject *const parent=this->parent;
640 SPObject *old_prev=NULL;
641 for ( SPObject *child = parent->children ; child && child != this ;
642 child = child->next )
643 {
644 old_prev = child;
645 }
647 SPObject *next=this->next;
648 if (old_prev) {
649 old_prev->next = next;
650 } else {
651 parent->children = next;
652 }
653 if (!next) {
654 parent->_last_child = old_prev;
655 }
656 if (prev) {
657 next = prev->next;
658 prev->next = this;
659 } else {
660 next = parent->children;
661 parent->children = this;
662 }
663 this->next = next;
664 if (!next) {
665 parent->_last_child = this;
666 }
667 }
669 /**
670 * Remove object from parent's children, release and unref it.
671 */
672 void SPObject::detach(SPObject *object) {
673 //g_return_if_fail(parent != NULL);
674 //g_return_if_fail(SP_IS_OBJECT(parent));
675 g_return_if_fail(object != NULL);
676 g_return_if_fail(SP_IS_OBJECT(object));
677 g_return_if_fail(object->parent == this);
679 object->releaseReferences();
681 SPObject *prev=NULL;
682 for ( SPObject *child = this->children ; child && child != object ;
683 child = child->next )
684 {
685 prev = child;
686 }
688 SPObject *next=object->next;
689 if (prev) {
690 prev->next = next;
691 } else {
692 this->children = next;
693 }
694 if (!next) {
695 this->_last_child = prev;
696 }
698 object->next = NULL;
699 object->parent = NULL;
701 this->_updateTotalHRefCount(-object->_total_hrefcount);
702 sp_object_unref(object, this);
703 }
705 /**
706 * Return object's child whose node pointer equals repr.
707 */
708 SPObject *SPObject::get_child_by_repr(Inkscape::XML::Node *repr)
709 {
710 g_return_val_if_fail(repr != NULL, NULL);
711 SPObject *result = 0;
713 if ( _last_child && (_last_child->getRepr() == repr) ) {
714 result = _last_child; // optimization for common scenario
715 } else {
716 for ( SPObject *child = children ; child ; child = child->next ) {
717 if ( child->getRepr() == repr ) {
718 result = child;
719 break;
720 }
721 }
722 }
723 return result;
724 }
726 /**
727 * Callback for child_added event.
728 * Invoked whenever the given mutation event happens in the XML tree.
729 */
730 void SPObject::sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
731 {
732 GType type = sp_repr_type_lookup(child);
733 if (!type) {
734 return;
735 }
736 SPObject *ochild = SP_OBJECT(g_object_new(type, 0));
737 SPObject *prev = ref ? object->get_child_by_repr(ref) : NULL;
738 object->attach(ochild, prev);
739 sp_object_unref(ochild, NULL);
741 ochild->invoke_build(object->document, child, object->cloned);
742 }
744 /**
745 * Removes, releases and unrefs all children of object.
746 *
747 * This is the opposite of build. It has to be invoked as soon as the
748 * object is removed from the tree, even if it is still alive according
749 * to reference count. The frontend unregisters the object from the
750 * document and releases the SPRepr bindings; implementations should free
751 * state data and release all child objects. Invoking release on
752 * SPRoot destroys the whole document tree.
753 * \see sp_object_build()
754 */
755 void SPObject::sp_object_release(SPObject *object)
756 {
757 debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
758 while (object->children) {
759 object->detach(object->children);
760 }
761 }
763 /**
764 * Remove object's child whose node equals repr, release and
765 * unref it.
766 *
767 * Invoked whenever the given mutation event happens in the XML
768 * tree, BEFORE removal from the XML tree happens, so grouping
769 * objects can safely release the child data.
770 */
771 void SPObject::sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child)
772 {
773 debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
774 SPObject *ochild = object->get_child_by_repr(child);
775 g_return_if_fail (ochild != NULL || !strcmp("comment", child->name())); // comments have no objects
776 if (ochild) {
777 object->detach(ochild);
778 }
779 }
781 /**
782 * Move object corresponding to child after sibling object corresponding
783 * to new_ref.
784 * Invoked whenever the given mutation event happens in the XML tree.
785 * \param old_ref Ignored
786 */
787 void SPObject::sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node */*old_ref*/,
788 Inkscape::XML::Node *new_ref)
789 {
790 SPObject *ochild = object->get_child_by_repr(child);
791 g_return_if_fail(ochild != NULL);
792 SPObject *prev = new_ref ? object->get_child_by_repr(new_ref) : NULL;
793 ochild->reorder(prev);
794 ochild->_position_changed_signal.emit(ochild);
795 }
797 /**
798 * Virtual build callback.
799 *
800 * This has to be invoked immediately after creation of an SPObject. The
801 * frontend method ensures that the new object is properly attached to
802 * the document and repr; implementation then will parse all of the attributes,
803 * generate the children objects and so on. Invoking build on the SPRoot
804 * object results in creation of the whole document tree (this is, what
805 * SPDocument does after the creation of the XML tree).
806 * \see sp_object_release()
807 */
808 void SPObject::sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
809 {
810 /* Nothing specific here */
811 debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
813 object->readAttr("xml:space");
814 object->readAttr("inkscape:label");
815 object->readAttr("inkscape:collect");
817 for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != NULL; rchild = rchild->next()) {
818 GType type = sp_repr_type_lookup(rchild);
819 if (!type) {
820 continue;
821 }
822 SPObject *child = SP_OBJECT(g_object_new(type, 0));
823 object->attach(child, object->lastChild());
824 sp_object_unref(child, NULL);
825 child->invoke_build(document, rchild, object->cloned);
826 }
827 }
829 void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
830 {
831 debug("id=%x, typename=%s", this, g_type_name_from_instance((GTypeInstance*)this));
833 //g_assert(object != NULL);
834 //g_assert(SP_IS_OBJECT(object));
835 g_assert(document != NULL);
836 g_assert(repr != NULL);
838 g_assert(this->document == NULL);
839 g_assert(this->repr == NULL);
840 g_assert(this->getId() == NULL);
842 /* Bookkeeping */
844 this->document = document;
845 this->repr = repr;
846 if (!cloned) {
847 Inkscape::GC::anchor(repr);
848 }
849 this->cloned = cloned;
851 if ( !cloned ) {
852 this->document->bindObjectToRepr(this->repr, this);
854 if (Inkscape::XML::id_permitted(this->repr)) {
855 /* If we are not cloned, and not seeking, force unique id */
856 gchar const *id = this->repr->attribute("id");
857 if (!document->isSeeking()) {
858 {
859 gchar *realid = sp_object_get_unique_id(this, id);
860 g_assert(realid != NULL);
862 this->document->bindObjectToId(realid, this);
863 SPObjectImpl::setId(this, realid);
864 g_free(realid);
865 }
867 /* Redefine ID, if required */
868 if ((id == NULL) || (strcmp(id, this->getId()) != 0)) {
869 this->repr->setAttribute("id", this->getId());
870 }
871 } else if (id) {
872 // bind if id, but no conflict -- otherwise, we can expect
873 // a subsequent setting of the id attribute
874 if (!this->document->getObjectById(id)) {
875 this->document->bindObjectToId(id, this);
876 SPObjectImpl::setId(this, id);
877 }
878 }
879 }
880 } else {
881 g_assert(this->getId() == NULL);
882 }
884 /* Invoke derived methods, if any */
885 if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->build) {
886 (*((SPObjectClass *) G_OBJECT_GET_CLASS(this))->build)(this, document, repr);
887 }
889 /* Signalling (should be connected AFTER processing derived methods */
890 sp_repr_add_listener(repr, &object_event_vector, this);
891 }
893 long long int SPObject::getIntAttribute(char const *key, long long int def)
894 {
895 return sp_repr_get_int_attribute(getRepr(),key,def);
896 }
898 unsigned SPObject::getPosition(){
899 g_assert(this->repr);
901 return repr->position();
902 }
904 void SPObject::appendChild(Inkscape::XML::Node *child) {
905 g_assert(this->repr);
907 repr->appendChild(child);
908 }
910 void SPObject::addChild(Inkscape::XML::Node *child, Inkscape::XML::Node * prev)
911 {
912 g_assert(this->repr);
914 repr->addChild(child,prev);
915 }
917 void SPObject::releaseReferences() {
918 g_assert(this->document);
919 g_assert(this->repr);
921 sp_repr_remove_listener_by_data(this->repr, this);
923 this->_release_signal.emit(this);
924 SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
925 if (klass->release) {
926 klass->release(this);
927 }
929 /* all hrefs should be released by the "release" handlers */
930 g_assert(this->hrefcount == 0);
932 if (!cloned) {
933 if (this->id) {
934 this->document->bindObjectToId(this->id, NULL);
935 }
936 g_free(this->id);
937 this->id = NULL;
939 g_free(this->_default_label);
940 this->_default_label = NULL;
942 this->document->bindObjectToRepr(this->repr, NULL);
944 Inkscape::GC::release(this->repr);
945 } else {
946 g_assert(!this->id);
947 }
949 if (this->style) {
950 this->style = sp_style_unref(this->style);
951 }
953 this->document = NULL;
954 this->repr = NULL;
955 }
958 SPObject *SPObject::getPrev()
959 {
960 SPObject *prev = 0;
961 for ( SPObject *obj = parent->firstChild(); obj && !prev; obj = obj->getNext() ) {
962 if (obj->getNext() == this) {
963 prev = obj;
964 }
965 }
966 return prev;
967 }
969 /**
970 * Callback for child_added node event.
971 */
972 void SPObject::sp_object_repr_child_added(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data)
973 {
974 SPObject *object = SP_OBJECT(data);
976 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->child_added) {
977 (*((SPObjectClass *)G_OBJECT_GET_CLASS(object))->child_added)(object, child, ref);
978 }
979 }
981 /**
982 * Callback for remove_child node event.
983 */
984 void SPObject::sp_object_repr_child_removed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, gpointer data)
985 {
986 SPObject *object = SP_OBJECT(data);
988 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->remove_child) {
989 (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->remove_child)(object, child);
990 }
991 }
993 /**
994 * Callback for order_changed node event.
995 *
996 * \todo fixme:
997 */
998 void SPObject::sp_object_repr_order_changed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data)
999 {
1000 SPObject *object = SP_OBJECT(data);
1002 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->order_changed) {
1003 (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->order_changed)(object, child, old, newer);
1004 }
1005 }
1007 /**
1008 * Callback for set event.
1009 */
1010 void SPObject::sp_object_private_set(SPObject *object, unsigned int key, gchar const *value)
1011 {
1012 g_assert(key != SP_ATTR_INVALID);
1014 switch (key) {
1015 case SP_ATTR_ID:
1017 //XML Tree being used here.
1018 if ( !object->cloned && object->getRepr()->type() == Inkscape::XML::ELEMENT_NODE ) {
1019 SPDocument *document=object->document;
1020 SPObject *conflict=NULL;
1022 gchar const *new_id = value;
1024 if (new_id) {
1025 conflict = document->getObjectById((char const *)new_id);
1026 }
1028 if ( conflict && conflict != object ) {
1029 if (!document->isSeeking()) {
1030 sp_object_ref(conflict, NULL);
1031 // give the conflicting object a new ID
1032 gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL);
1033 conflict->getRepr()->setAttribute("id", new_conflict_id);
1034 g_free(new_conflict_id);
1035 sp_object_unref(conflict, NULL);
1036 } else {
1037 new_id = NULL;
1038 }
1039 }
1041 if (object->getId()) {
1042 document->bindObjectToId(object->getId(), NULL);
1043 SPObjectImpl::setId(object, 0);
1044 }
1046 if (new_id) {
1047 SPObjectImpl::setId(object, new_id);
1048 document->bindObjectToId(object->getId(), object);
1049 }
1051 g_free(object->_default_label);
1052 object->_default_label = NULL;
1053 }
1054 break;
1055 case SP_ATTR_INKSCAPE_LABEL:
1056 g_free(object->_label);
1057 if (value) {
1058 object->_label = g_strdup(value);
1059 } else {
1060 object->_label = NULL;
1061 }
1062 g_free(object->_default_label);
1063 object->_default_label = NULL;
1064 break;
1065 case SP_ATTR_INKSCAPE_COLLECT:
1066 if ( value && !strcmp(value, "always") ) {
1067 object->setCollectionPolicy(SPObject::ALWAYS_COLLECT);
1068 } else {
1069 object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT);
1070 }
1071 break;
1072 case SP_ATTR_XML_SPACE:
1073 if (value && !strcmp(value, "preserve")) {
1074 object->xml_space.value = SP_XML_SPACE_PRESERVE;
1075 object->xml_space.set = TRUE;
1076 } else if (value && !strcmp(value, "default")) {
1077 object->xml_space.value = SP_XML_SPACE_DEFAULT;
1078 object->xml_space.set = TRUE;
1079 } else if (object->parent) {
1080 SPObject *parent;
1081 parent = object->parent;
1082 object->xml_space.value = parent->xml_space.value;
1083 }
1084 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1085 break;
1086 case SP_ATTR_STYLE:
1087 sp_style_read_from_object(object->style, object);
1088 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1089 break;
1090 default:
1091 break;
1092 }
1093 }
1095 /**
1096 * Call virtual set() function of object.
1097 */
1098 void SPObject::setKeyValue(unsigned int key, gchar const *value)
1099 {
1100 //g_assert(object != NULL);
1101 //g_assert(SP_IS_OBJECT(object));
1103 if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->set) {
1104 ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->set(this, key, value);
1105 }
1106 }
1108 /**
1109 * Read value of key attribute from XML node into object.
1110 */
1111 void SPObject::readAttr(gchar const *key)
1112 {
1113 //g_assert(object != NULL);
1114 //g_assert(SP_IS_OBJECT(object));
1115 g_assert(key != NULL);
1117 //XML Tree being used here.
1118 g_assert(this->getRepr() != NULL);
1120 unsigned int keyid = sp_attribute_lookup(key);
1121 if (keyid != SP_ATTR_INVALID) {
1122 /* Retrieve the 'key' attribute from the object's XML representation */
1123 gchar const *value = getRepr()->attribute(key);
1125 setKeyValue(keyid, value);
1126 }
1127 }
1129 /**
1130 * Callback for attr_changed node event.
1131 */
1132 void SPObject::sp_object_repr_attr_changed(Inkscape::XML::Node */*repr*/, gchar const *key, gchar const */*oldval*/, gchar const */*newval*/, bool is_interactive, gpointer data)
1133 {
1134 SPObject *object = SP_OBJECT(data);
1136 object->readAttr(key);
1138 // manual changes to extension attributes require the normal
1139 // attributes, which depend on them, to be updated immediately
1140 if (is_interactive) {
1141 object->updateRepr(0);
1142 }
1143 }
1145 /**
1146 * Callback for content_changed node event.
1147 */
1148 void SPObject::sp_object_repr_content_changed(Inkscape::XML::Node */*repr*/, gchar const */*oldcontent*/, gchar const */*newcontent*/, gpointer data)
1149 {
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);
1154 }
1155 }
1157 /**
1158 * Return string representation of space value.
1159 */
1160 static gchar const*
1161 sp_xml_get_space_string(unsigned int space)
1162 {
1163 switch (space) {
1164 case SP_XML_SPACE_DEFAULT:
1165 return "default";
1166 case SP_XML_SPACE_PRESERVE:
1167 return "preserve";
1168 default:
1169 return NULL;
1170 }
1171 }
1173 /**
1174 * Callback for write event.
1175 */
1176 Inkscape::XML::Node * SPObject::sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags)
1177 {
1178 if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) {
1179 repr = object->getRepr()->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 = object->style;
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;
1234 }
1236 /**
1237 * Update this object's XML node with flags value.
1238 */
1239 Inkscape::XML::Node * SPObject::updateRepr(unsigned int flags) {
1240 if ( !cloned ) {
1241 Inkscape::XML::Node *repr = getRepr();
1242 if (repr) {
1243 return updateRepr(repr->document(), repr, flags);
1244 } else {
1245 g_critical("Attempt to update non-existent repr");
1246 return NULL;
1247 }
1248 } else {
1249 /* cloned objects have no repr */
1250 return NULL;
1251 }
1252 }
1254 /** Used both to create reprs in the original document, and to create
1255 * reprs in another document (e.g. a temporary document used when
1256 * saving as "Plain SVG"
1257 */
1258 Inkscape::XML::Node * SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) {
1259 g_assert(doc != NULL);
1261 if (cloned) {
1262 /* cloned objects have no repr */
1263 return NULL;
1264 }
1265 if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write) {
1266 if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1267 repr = getRepr();
1268 }
1269 return ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write(this, doc, repr, flags);
1270 } else {
1271 g_warning("Class %s does not implement ::write", G_OBJECT_TYPE_NAME(this));
1272 if (!repr) {
1273 if (flags & SP_OBJECT_WRITE_BUILD) {
1274 repr = getRepr()->duplicate(doc);
1275 }
1276 /// \todo FIXME: else probably error (Lauris) */
1277 } else {
1278 repr->mergeFrom(getRepr(), "id");
1279 }
1280 return repr;
1281 }
1282 }
1284 /* Modification */
1286 /**
1287 * Add \a flags to \a object's as dirtiness flags, and
1288 * recursively add CHILD_MODIFIED flag to
1289 * parent and ancestors (as far up as necessary).
1290 */
1291 void SPObject::requestDisplayUpdate(unsigned int flags)
1292 {
1293 g_return_if_fail( this->document != NULL );
1295 if (update_in_progress) {
1296 g_print("WARNING: Requested update while update in progress, counter = %d\n", update_in_progress);
1297 }
1299 /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1300 * SP_OBJECT_CHILD_MODIFIED_FLAG */
1301 g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1302 g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1303 g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1305 bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1307 this->uflags |= flags;
1309 /* If requestModified has already been called on this object or one of its children, then we
1310 * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1311 */
1312 if (already_propagated) {
1313 if (parent) {
1314 parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
1315 } else {
1316 document->requestModified();
1317 }
1318 }
1319 }
1321 /**
1322 * Update views
1323 */
1324 void SPObject::updateDisplay(SPCtx *ctx, unsigned int flags)
1325 {
1326 g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1328 update_in_progress ++;
1330 #ifdef SP_OBJECT_DEBUG_CASCADE
1331 g_print("Update %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), getId(), flags, this->uflags, this->mflags);
1332 #endif
1334 /* Get this flags */
1335 flags |= this->uflags;
1336 /* Copy flags to modified cascade for later processing */
1337 this->mflags |= this->uflags;
1338 /* We have to clear flags here to allow rescheduling update */
1339 this->uflags = 0;
1341 // Merge style if we have good reasons to think that parent style is changed */
1342 /** \todo
1343 * I am not sure whether we should check only propagated
1344 * flag. We are currently assuming that style parsing is
1345 * done immediately. I think this is correct (Lauris).
1346 */
1347 if ((flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1348 if (this->style && this->parent) {
1349 sp_style_merge_from_parent(this->style, this->parent->style);
1350 }
1351 }
1353 try
1354 {
1355 if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update) {
1356 ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);
1357 }
1358 }
1359 catch(...)
1360 {
1361 /** \todo
1362 * in case of catching an exception we need to inform the user somehow that the document is corrupted
1363 * maybe by implementing an document flag documentOk
1364 * or by a modal error dialog
1365 */
1366 g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);");
1367 }
1369 update_in_progress --;
1370 }
1372 /**
1373 * Request modified always bubbles *up* the tree, as opposed to
1374 * request display update, which trickles down and relies on the
1375 * flags set during this pass...
1376 */
1377 void SPObject::requestModified(unsigned int flags)
1378 {
1379 g_return_if_fail( this->document != NULL );
1381 /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1382 * SP_OBJECT_CHILD_MODIFIED_FLAG */
1383 g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1384 g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1385 g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1387 bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1389 this->mflags |= flags;
1391 /* If requestModified has already been called on this object or one of its children, then we
1392 * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1393 */
1394 if (already_propagated) {
1395 if (parent) {
1396 parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
1397 } else {
1398 document->requestModified();
1399 }
1400 }
1401 }
1403 /**
1404 * Emits the MODIFIED signal with the object's flags.
1405 * The object's mflags are the original set aside during the update pass for
1406 * later delivery here. Once emitModified() is called, those flags don't
1407 * need to be stored any longer.
1408 */
1409 void SPObject::emitModified(unsigned int flags)
1410 {
1411 /* only the MODIFIED_CASCADE flag is legal here */
1412 g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1414 #ifdef SP_OBJECT_DEBUG_CASCADE
1415 g_print("Modified %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), getId(), flags, this->uflags, this->mflags);
1416 #endif
1418 flags |= this->mflags;
1419 /* We have to clear mflags beforehand, as signal handlers may
1420 * make changes and therefore queue new modification notifications
1421 * themselves. */
1422 this->mflags = 0;
1424 g_object_ref(G_OBJECT(this));
1425 SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
1426 if (klass->modified) {
1427 klass->modified(this, flags);
1428 }
1429 _modified_signal.emit(this, flags);
1430 g_object_unref(G_OBJECT(this));
1431 }
1433 gchar const *SPObject::getTagName(SPException *ex) const
1434 {
1435 g_assert(repr != NULL);
1436 /* If exception is not clear, return */
1437 if (!SP_EXCEPTION_IS_OK(ex)) {
1438 return NULL;
1439 }
1441 /// \todo fixme: Exception if object is NULL? */
1442 //XML Tree being used here.
1443 return getRepr()->name();
1444 }
1446 gchar const *SPObject::getAttribute(gchar const *key, SPException *ex) const
1447 {
1448 g_assert(this->repr != NULL);
1449 /* If exception is not clear, return */
1450 if (!SP_EXCEPTION_IS_OK(ex)) {
1451 return NULL;
1452 }
1454 /// \todo fixme: Exception if object is NULL? */
1455 //XML Tree being used here.
1456 return (gchar const *) getRepr()->attribute(key);
1457 }
1459 void SPObject::setAttribute(gchar const *key, gchar const *value, SPException *ex)
1460 {
1461 g_assert(this->repr != NULL);
1462 /* If exception is not clear, return */
1463 g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1465 /// \todo fixme: Exception if object is NULL? */
1466 //XML Tree being used here.
1467 getRepr()->setAttribute(key, value, false);
1468 }
1470 void SPObject::removeAttribute(gchar const *key, SPException *ex)
1471 {
1472 /* If exception is not clear, return */
1473 g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1475 /// \todo fixme: Exception if object is NULL? */
1476 //XML Tree being used here.
1477 getRepr()->setAttribute(key, NULL, false);
1478 }
1480 bool SPObject::storeAsDouble( gchar const *key, double *val ) const
1481 {
1482 g_assert(this->getRepr()!= NULL);
1483 return sp_repr_get_double(((Inkscape::XML::Node *)(this->getRepr())),key,val);
1484 }
1486 /* Helper */
1488 gchar * SPObject::sp_object_get_unique_id(SPObject *object, gchar const *id)
1489 {
1490 static unsigned long count = 0;
1492 g_assert(SP_IS_OBJECT(object));
1494 count++;
1496 //XML Tree being used here.
1497 gchar const *name = object->getRepr()->name();
1498 g_assert(name != NULL);
1500 gchar const *local = strchr(name, ':');
1501 if (local) {
1502 name = local + 1;
1503 }
1505 if (id != NULL) {
1506 if (object->document->getObjectById(id) == NULL) {
1507 return g_strdup(id);
1508 }
1509 }
1511 size_t const name_len = strlen(name);
1512 size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1;
1513 gchar *const buf = (gchar *) g_malloc(buflen);
1514 memcpy(buf, name, name_len);
1515 gchar *const count_buf = buf + name_len;
1516 size_t const count_buflen = buflen - name_len;
1517 do {
1518 ++count;
1519 g_snprintf(count_buf, count_buflen, "%lu", count);
1520 } while ( object->document->getObjectById(buf) != NULL );
1521 return buf;
1522 }
1524 /* Style */
1526 /**
1527 * Returns an object style property.
1528 *
1529 * \todo
1530 * fixme: Use proper CSS parsing. The current version is buggy
1531 * in a number of situations where key is a substring of the
1532 * style string other than as a property name (including
1533 * where key is a substring of a property name), and is also
1534 * buggy in its handling of inheritance for properties that
1535 * aren't inherited by default. It also doesn't allow for
1536 * the case where the property is specified but with an invalid
1537 * value (in which case I believe the CSS2 error-handling
1538 * behaviour applies, viz. behave as if the property hadn't
1539 * been specified). Also, the current code doesn't use CRSelEng
1540 * stuff to take a value from stylesheets. Also, we aren't
1541 * setting any hooks to force an update for changes in any of
1542 * the inputs (i.e., in any of the elements that this function
1543 * queries).
1544 *
1545 * \par
1546 * Given that the default value for a property depends on what
1547 * property it is (e.g., whether to inherit or not), and given
1548 * the above comment about ignoring invalid values, and that the
1549 * repr parent isn't necessarily the right element to inherit
1550 * from (e.g., maybe we need to inherit from the referencing
1551 * <use> element instead), we should probably make the caller
1552 * responsible for ascending the repr tree as necessary.
1553 */
1554 gchar const * SPObject::getStyleProperty(gchar const *key, gchar const *def) const
1555 {
1556 //g_return_val_if_fail(object != NULL, NULL);
1557 //g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
1558 g_return_val_if_fail(key != NULL, NULL);
1560 //XML Tree being used here.
1561 gchar const *style = getRepr()->attribute("style");
1562 if (style) {
1563 size_t const len = strlen(key);
1564 char const *p;
1565 while ( (p = strstr(style, key))
1566 != NULL )
1567 {
1568 p += len;
1569 while ((*p <= ' ') && *p) {
1570 p++;
1571 }
1572 if (*p++ != ':') {
1573 break;
1574 }
1575 while ((*p <= ' ') && *p) {
1576 p++;
1577 }
1578 size_t const inherit_len = sizeof("inherit") - 1;
1579 if (*p
1580 && !(strneq(p, "inherit", inherit_len)
1581 && (p[inherit_len] == '\0'
1582 || p[inherit_len] == ';'
1583 || g_ascii_isspace(p[inherit_len])))) {
1584 return p;
1585 }
1586 }
1587 }
1589 //XML Tree being used here.
1590 gchar const *val = getRepr()->attribute(key);
1591 if (val && !streq(val, "inherit")) {
1592 return val;
1593 }
1594 if (this->parent) {
1595 return (this->parent)->getStyleProperty(key, def);
1596 }
1598 return def;
1599 }
1601 /**
1602 * Lifts SVG version of all root objects to version.
1603 */
1604 void SPObject::_requireSVGVersion(Inkscape::Version version) {
1605 for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) {
1606 SPObject *object=iter;
1607 if (SP_IS_ROOT(object)) {
1608 SPRoot *root=SP_ROOT(object);
1609 if ( root->version.svg < version ) {
1610 root->version.svg = version;
1611 }
1612 }
1613 }
1614 }
1616 /* Titles and descriptions */
1618 /* Note:
1619 Titles and descriptions are stored in 'title' and 'desc' child elements
1620 (see section 5.4 of the SVG 1.0 and 1.1 specifications). The spec allows
1621 an element to have more than one 'title' child element, but strongly
1622 recommends against this and requires using the first one if a choice must
1623 be made. The same applies to 'desc' elements. Therefore, these functions
1624 ignore all but the first 'title' child element and first 'desc' child
1625 element, except when deleting a title or description.
1626 */
1628 /**
1629 * Returns the title of this object, or NULL if there is none.
1630 * The caller must free the returned string using g_free() - see comment
1631 * for getTitleOrDesc() below.
1632 */
1633 gchar * SPObject::title() const
1634 {
1635 return getTitleOrDesc("svg:title");
1636 }
1638 /**
1639 * Sets the title of this object
1640 * A NULL first argument is interpreted as meaning that the existing title
1641 * (if any) should be deleted.
1642 * The second argument is optional - see setTitleOrDesc() below for details.
1643 */
1644 bool SPObject::setTitle(gchar const *title, bool verbatim)
1645 {
1646 return setTitleOrDesc(title, "svg:title", verbatim);
1647 }
1649 /**
1650 * Returns the description of this object, or NULL if there is none.
1651 * The caller must free the returned string using g_free() - see comment
1652 * for getTitleOrDesc() below.
1653 */
1654 gchar * SPObject::desc() const
1655 {
1656 return getTitleOrDesc("svg:desc");
1657 }
1659 /**
1660 * Sets the description of this object.
1661 * A NULL first argument is interpreted as meaning that the existing
1662 * description (if any) should be deleted.
1663 * The second argument is optional - see setTitleOrDesc() below for details.
1664 */
1665 bool SPObject::setDesc(gchar const *desc, bool verbatim)
1666 {
1667 return setTitleOrDesc(desc, "svg:desc", verbatim);
1668 }
1670 /**
1671 * Returns the title or description of this object, or NULL if there is none.
1672 *
1673 * The SVG spec allows 'title' and 'desc' elements to contain text marked up
1674 * using elements from other namespaces. Therefore, this function cannot
1675 * in general just return a pointer to an existing string - it must instead
1676 * construct a string containing the title or description without the mark-up.
1677 * Consequently, the return value is a newly allocated string (or NULL), and
1678 * must be freed (using g_free()) by the caller.
1679 */
1680 gchar * SPObject::getTitleOrDesc(gchar const *svg_tagname) const
1681 {
1682 gchar *result = 0;
1683 SPObject *elem = findFirstChild(svg_tagname);
1684 if ( elem ) {
1685 result = g_string_free(elem->textualContent(), FALSE);
1686 }
1687 return result;
1688 }
1690 /**
1691 * Sets or deletes the title or description of this object.
1692 * A NULL 'value' argument causes the title or description to be deleted.
1693 *
1694 * 'verbatim' parameter:
1695 * If verbatim==true, then the title or description is set to exactly the
1696 * specified value. If verbatim==false then two exceptions are made:
1697 * (1) If the specified value is just whitespace, then the title/description
1698 * is deleted.
1699 * (2) If the specified value is the same as the current value except for
1700 * mark-up, then the current value is left unchanged.
1701 * This is usually the desired behaviour, so 'verbatim' defaults to false for
1702 * setTitle() and setDesc().
1703 *
1704 * The return value is true if a change was made to the title/description,
1705 * and usually false otherwise.
1706 */
1707 bool SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool verbatim)
1708 {
1709 if (!verbatim) {
1710 // If the new title/description is just whitespace,
1711 // treat it as though it were NULL.
1712 if (value) {
1713 bool just_whitespace = true;
1714 for (const gchar *cp = value; *cp; ++cp) {
1715 if (!std::strchr("\r\n \t", *cp)) {
1716 just_whitespace = false;
1717 break;
1718 }
1719 }
1720 if (just_whitespace) {
1721 value = NULL;
1722 }
1723 }
1724 // Don't stomp on mark-up if there is no real change.
1725 if (value) {
1726 gchar *current_value = getTitleOrDesc(svg_tagname);
1727 if (current_value) {
1728 bool different = std::strcmp(current_value, value);
1729 g_free(current_value);
1730 if (!different) {
1731 return false;
1732 }
1733 }
1734 }
1735 }
1737 SPObject *elem = findFirstChild(svg_tagname);
1739 if (value == NULL) {
1740 if (elem == NULL) {
1741 return false;
1742 }
1743 // delete the title/description(s)
1744 while (elem) {
1745 elem->deleteObject();
1746 elem = findFirstChild(svg_tagname);
1747 }
1748 return true;
1749 }
1751 Inkscape::XML::Document *xml_doc = document->getReprDoc();
1753 if (elem == NULL) {
1754 // create a new 'title' or 'desc' element, putting it at the
1755 // beginning (in accordance with the spec's recommendations)
1756 Inkscape::XML::Node *xml_elem = xml_doc->createElement(svg_tagname);
1757 repr->addChild(xml_elem, NULL);
1758 elem = document->getObjectByRepr(xml_elem);
1759 Inkscape::GC::release(xml_elem);
1760 }
1761 else {
1762 // remove the current content of the 'text' or 'desc' element
1763 SPObject *child;
1764 while (NULL != (child = elem->firstChild())) child->deleteObject();
1765 }
1767 // add the new content
1768 elem->appendChildRepr(xml_doc->createTextNode(value));
1769 return true;
1770 }
1772 /**
1773 * Find the first child of this object with a given tag name,
1774 * and return it. Returns NULL if there is no matching child.
1775 */
1776 SPObject * SPObject::findFirstChild(gchar const *tagname) const
1777 {
1778 for (SPObject *child = children; child; child = child->next)
1779 {
1780 if (child->repr->type() == Inkscape::XML::ELEMENT_NODE &&
1781 !strcmp(child->repr->name(), tagname)) {
1782 return child;
1783 }
1784 }
1785 return NULL;
1786 }
1788 /**
1789 * Return the full textual content of an element (typically all the
1790 * content except the tags).
1791 * Must not be used on anything except elements.
1792 */
1793 GString * SPObject::textualContent() const
1794 {
1795 GString* text = g_string_new("");
1797 for (const SPObject *child = firstChild(); child; child = child->next)
1798 {
1799 Inkscape::XML::NodeType child_type = child->repr->type();
1801 if (child_type == Inkscape::XML::ELEMENT_NODE) {
1802 GString * new_text = child->textualContent();
1803 g_string_append(text, new_text->str);
1804 g_string_free(new_text, TRUE);
1805 }
1806 else if (child_type == Inkscape::XML::TEXT_NODE) {
1807 g_string_append(text, child->repr->content());
1808 }
1809 }
1810 return text;
1811 }
1813 /*
1814 Local Variables:
1815 mode:c++
1816 c-file-style:"stroustrup"
1817 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1818 indent-tabs-mode:nil
1819 fill-column:99
1820 End:
1821 */
1822 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :