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