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 *
9 * Copyright (C) 1999-2005 authors
10 * Copyright (C) 2001-2002 Ximian, Inc.
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
15 /** \class SPObject
16 *
17 * SPObject is an abstract base class of all of the document nodes at the
18 * SVG document level. Each SPObject subclass implements a certain SVG
19 * element node type, or is an abstract base class for different node
20 * types. The SPObject layer is bound to the SPRepr layer, closely
21 * following the SPRepr mutations via callbacks. During creation,
22 * SPObject parses and interprets all textual attributes and CSS style
23 * strings of the SPRepr, and later updates the internal state whenever
24 * it receives a signal about a change. The opposite is not true - there
25 * are methods manipulating SPObjects directly and such changes do not
26 * propagate to the SPRepr layer. This is important for implementation of
27 * the undo stack, animations and other features.
28 *
29 * SPObjects are bound to the higher-level container SPDocument, which
30 * provides document level functionality such as the undo stack,
31 * dictionary and so on. Source: doc/architecture.txt
32 */
35 #include "helper/sp-marshal.h"
36 #include "xml/node-event-vector.h"
37 #include "attributes.h"
38 #include "document.h"
39 #include "style.h"
40 #include "sp-object-repr.h"
41 #include "sp-root.h"
42 #include "streq.h"
43 #include "strneq.h"
44 #include "xml/repr.h"
45 #include "xml/node-fns.h"
46 #include "debug/event-tracker.h"
48 #include "algorithms/longest-common-suffix.h"
49 using std::memcpy;
50 using std::strchr;
51 using std::strcmp;
52 using std::strlen;
53 using std::strstr;
55 #define noSP_OBJECT_DEBUG_CASCADE
57 #define noSP_OBJECT_DEBUG
59 #ifdef SP_OBJECT_DEBUG
60 # define debug(f, a...) { g_print("%s(%d) %s:", \
61 __FILE__,__LINE__,__FUNCTION__); \
62 g_print(f, ## a); \
63 g_print("\n"); \
64 }
65 #else
66 # define debug(f, a...) /**/
67 #endif
69 static void sp_object_class_init(SPObjectClass *klass);
70 static void sp_object_init(SPObject *object);
71 static void sp_object_finalize(GObject *object);
73 static void sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref);
74 static void sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child);
75 static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref);
77 static void sp_object_release(SPObject *object);
78 static void sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr);
80 static void sp_object_private_set(SPObject *object, unsigned int key, gchar const *value);
81 static Inkscape::XML::Node *sp_object_private_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
83 /* Real handlers of repr signals */
85 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);
87 static void sp_object_repr_content_changed(Inkscape::XML::Node *repr, gchar const *oldcontent, gchar const *newcontent, gpointer data);
89 static void sp_object_repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data);
90 static void sp_object_repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data);
92 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);
94 static gchar *sp_object_get_unique_id(SPObject *object, gchar const *defid);
96 guint update_in_progress = 0; // guard against update-during-update
98 enum {RELEASE, MODIFIED, LAST_SIGNAL};
100 Inkscape::XML::NodeEventVector object_event_vector = {
101 sp_object_repr_child_added,
102 sp_object_repr_child_removed,
103 sp_object_repr_attr_changed,
104 sp_object_repr_content_changed,
105 sp_object_repr_order_changed
106 };
108 static GObjectClass *parent_class;
109 static guint object_signals[LAST_SIGNAL] = {0};
111 /**
112 * Registers the SPObject class with Gdk and returns its type number.
113 */
114 GType
115 sp_object_get_type(void)
116 {
117 static GType type = 0;
118 if (!type) {
119 GTypeInfo info = {
120 sizeof(SPObjectClass),
121 NULL, NULL,
122 (GClassInitFunc) sp_object_class_init,
123 NULL, NULL,
124 sizeof(SPObject),
125 16,
126 (GInstanceInitFunc) sp_object_init,
127 NULL
128 };
129 type = g_type_register_static(G_TYPE_OBJECT, "SPObject", &info, (GTypeFlags)0);
130 }
131 return type;
132 }
134 /**
135 * Initializes the SPObject vtable.
136 */
137 static void
138 sp_object_class_init(SPObjectClass *klass)
139 {
140 GObjectClass *object_class;
142 object_class = (GObjectClass *) klass;
144 parent_class = (GObjectClass *) g_type_class_ref(G_TYPE_OBJECT);
146 object_signals[RELEASE] = g_signal_new("release",
147 G_TYPE_FROM_CLASS(klass),
148 (GSignalFlags)(G_SIGNAL_RUN_CLEANUP | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS),
149 G_STRUCT_OFFSET(SPObjectClass, release),
150 NULL, NULL,
151 sp_marshal_VOID__VOID,
152 G_TYPE_NONE, 0);
153 object_signals[MODIFIED] = g_signal_new("modified",
154 G_TYPE_FROM_CLASS(klass),
155 G_SIGNAL_RUN_FIRST,
156 G_STRUCT_OFFSET(SPObjectClass, modified),
157 NULL, NULL,
158 sp_marshal_NONE__UINT,
159 G_TYPE_NONE, 1, G_TYPE_UINT);
161 object_class->finalize = sp_object_finalize;
163 klass->child_added = sp_object_child_added;
164 klass->remove_child = sp_object_remove_child;
165 klass->order_changed = sp_object_order_changed;
167 klass->release = sp_object_release;
169 klass->build = sp_object_build;
171 klass->set = sp_object_private_set;
172 klass->write = sp_object_private_write;
173 }
175 /**
176 * Callback to initialize the SPObject object.
177 */
178 static void
179 sp_object_init(SPObject *object)
180 {
181 debug("id=%x, typename=%s",object, g_type_name_from_instance((GTypeInstance*)object));
183 object->hrefcount = 0;
184 object->_total_hrefcount = 0;
185 object->document = NULL;
186 object->children = object->_last_child = NULL;
187 object->parent = object->next = NULL;
188 object->repr = NULL;
189 object->id = NULL;
190 object->style = NULL;
192 object->_collection_policy = SPObject::COLLECT_WITH_PARENT;
194 new (&object->_delete_signal) sigc::signal<void, SPObject *>();
195 object->_successor = NULL;
197 object->_label = NULL;
198 object->_default_label = NULL;
199 }
201 /**
202 * Callback to destroy all members and connections of object and itself.
203 */
204 static void
205 sp_object_finalize(GObject *object)
206 {
207 SPObject *spobject = (SPObject *)object;
209 g_free(spobject->_label);
210 g_free(spobject->_default_label);
211 spobject->_label = NULL;
212 spobject->_default_label = NULL;
214 if (spobject->_successor) {
215 sp_object_unref(spobject->_successor, NULL);
216 spobject->_successor = NULL;
217 }
219 if (((GObjectClass *) (parent_class))->finalize) {
220 (* ((GObjectClass *) (parent_class))->finalize)(object);
221 }
223 spobject->_delete_signal.~signal();
224 }
226 namespace {
228 Inkscape::Util::shared_ptr<char> stringify(SPObject *obj) {
229 char *temp=g_strdup_printf("%p", obj);
230 Inkscape::Util::shared_ptr<char> result=Inkscape::Util::share_string(temp);
231 g_free(temp);
232 return result;
233 }
235 Inkscape::Util::shared_ptr<char> stringify(unsigned n) {
236 char *temp=g_strdup_printf("%u", n);
237 Inkscape::Util::shared_ptr<char> result=Inkscape::Util::share_string(temp);
238 g_free(temp);
239 return result;
240 }
242 class RefEvent : public Inkscape::Debug::Event {
243 public:
244 enum Type { REF, UNREF };
246 RefEvent(SPObject *object, Type type)
247 : _object(stringify(object)), _refcount(G_OBJECT(object)->ref_count),
248 _type(type)
249 {}
251 static Category category() { return REFCOUNT; }
253 Inkscape::Util::shared_ptr<char> name() const {
254 if ( _type == REF) {
255 return Inkscape::Util::share_static_string("sp-object-ref");
256 } else {
257 return Inkscape::Util::share_static_string("sp-object-unref");
258 }
259 }
260 unsigned propertyCount() const { return 2; }
261 PropertyPair property(unsigned index) const {
262 switch (index) {
263 case 0:
264 return PropertyPair("object", _object);
265 case 1:
266 return PropertyPair("refcount", stringify( _type == REF ? _refcount + 1 : _refcount - 1 ));
267 default:
268 return PropertyPair();
269 }
270 }
272 private:
273 Inkscape::Util::shared_ptr<char> _object;
274 unsigned _refcount;
275 Type _type;
276 };
278 }
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 *
288 sp_object_ref(SPObject *object, SPObject *owner)
289 {
290 g_return_val_if_fail(object != NULL, NULL);
291 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
292 g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
294 Inkscape::Debug::EventTracker<> tracker;
295 tracker.set<RefEvent>(object, RefEvent::REF);
297 g_object_ref(G_OBJECT(object));
299 return object;
300 }
302 /**
303 * Decrease reference count of object, with possible debugging and
304 * finalization.
305 *
306 * \param owner If non-NULL, make debug log entry.
307 * \return always NULL
308 * \pre object points to real object
309 */
310 SPObject *
311 sp_object_unref(SPObject *object, SPObject *owner)
312 {
313 g_return_val_if_fail(object != NULL, NULL);
314 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
315 g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
317 Inkscape::Debug::EventTracker<> tracker;
318 tracker.set<RefEvent>(object, RefEvent::UNREF);
320 g_object_unref(G_OBJECT(object));
322 return NULL;
323 }
325 /**
326 * Increase weak refcount.
327 *
328 * Hrefcount is used for weak references, for example, to
329 * determine whether any graphical element references a certain gradient
330 * node.
331 * \param owner Ignored.
332 * \return object, NULL is error
333 * \pre object points to real object
334 */
335 SPObject *
336 sp_object_href(SPObject *object, gpointer owner)
337 {
338 g_return_val_if_fail(object != NULL, NULL);
339 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
341 object->hrefcount++;
342 object->_updateTotalHRefCount(1);
344 return object;
345 }
347 /**
348 * Decrease weak refcount.
349 *
350 * Hrefcount is used for weak references, for example, to determine whether
351 * any graphical element references a certain gradient node.
352 * \param owner Ignored.
353 * \return always NULL
354 * \pre object points to real object and hrefcount>0
355 */
356 SPObject *
357 sp_object_hunref(SPObject *object, gpointer owner)
358 {
359 g_return_val_if_fail(object != NULL, NULL);
360 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
361 g_return_val_if_fail(object->hrefcount > 0, NULL);
363 object->hrefcount--;
364 object->_updateTotalHRefCount(-1);
366 return NULL;
367 }
369 /**
370 * Adds increment to _total_hrefcount of object and its parents.
371 */
372 void
373 SPObject::_updateTotalHRefCount(int increment) {
374 SPObject *topmost_collectable = NULL;
375 for ( SPObject *iter = this ; iter ; iter = SP_OBJECT_PARENT(iter) ) {
376 iter->_total_hrefcount += increment;
377 if ( iter->_total_hrefcount < iter->hrefcount ) {
378 g_critical("HRefs overcounted");
379 }
380 if ( iter->_total_hrefcount == 0 &&
381 iter->_collection_policy != COLLECT_WITH_PARENT )
382 {
383 topmost_collectable = iter;
384 }
385 }
386 if (topmost_collectable) {
387 topmost_collectable->requestOrphanCollection();
388 }
389 }
391 /**
392 * True if object is non-NULL and this is some in/direct parent of object.
393 */
394 bool
395 SPObject::isAncestorOf(SPObject const *object) const {
396 g_return_val_if_fail(object != NULL, false);
397 object = SP_OBJECT_PARENT(object);
398 while (object) {
399 if ( object == this ) {
400 return true;
401 }
402 object = SP_OBJECT_PARENT(object);
403 }
404 return false;
405 }
407 namespace {
409 bool same_objects(SPObject const &a, SPObject const &b) {
410 return &a == &b;
411 }
413 }
415 /**
416 * Returns youngest object being parent to this and object.
417 */
418 SPObject const *
419 SPObject::nearestCommonAncestor(SPObject const *object) const {
420 g_return_val_if_fail(object != NULL, NULL);
422 using Inkscape::Algorithms::longest_common_suffix;
423 return longest_common_suffix<SPObject::ConstParentIterator>(this, object, NULL, &same_objects);
424 }
426 SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) {
427 if (obj == NULL || ancestor == NULL)
428 return NULL;
429 if (SP_OBJECT_PARENT(obj) == ancestor)
430 return obj;
431 return AncestorSon(SP_OBJECT_PARENT(obj), ancestor);
432 }
434 /**
435 * Compares height of objects in tree.
436 *
437 * Works for different-parent objects, so long as they have a common ancestor.
438 * \return \verbatim
439 * 0 positions are equivalent
440 * 1 first object's position is greater than the second
441 * -1 first object's position is less than the second \endverbatim
442 */
443 int
444 sp_object_compare_position(SPObject const *first, SPObject const *second)
445 {
446 if (first == second) return 0;
448 SPObject const *ancestor = first->nearestCommonAncestor(second);
449 if (ancestor == NULL) return 0; // cannot compare, no common ancestor!
451 // we have an object and its ancestor (should not happen when sorting selection)
452 if (ancestor == first)
453 return 1;
454 if (ancestor == second)
455 return -1;
457 SPObject const *to_first = AncestorSon(first, ancestor);
458 SPObject const *to_second = AncestorSon(second, ancestor);
460 g_assert(SP_OBJECT_PARENT(to_second) == SP_OBJECT_PARENT(to_first));
462 return sp_repr_compare_position(SP_OBJECT_REPR(to_first), SP_OBJECT_REPR(to_second));
463 }
466 /**
467 * Append repr as child of this object.
468 * \pre this is not a cloned object
469 */
470 SPObject *
471 SPObject::appendChildRepr(Inkscape::XML::Node *repr) {
472 if (!SP_OBJECT_IS_CLONED(this)) {
473 SP_OBJECT_REPR(this)->appendChild(repr);
474 return SP_OBJECT_DOCUMENT(this)->getObjectByRepr(repr);
475 } else {
476 g_critical("Attempt to append repr as child of cloned object");
477 return NULL;
478 }
479 }
481 /** Gets the label property for the object or a default if no label
482 * is defined.
483 */
484 gchar const *
485 SPObject::label() const {
486 return _label;
487 }
489 /** Returns a default label property for the object. */
490 gchar const *
491 SPObject::defaultLabel() const {
492 if (_label) {
493 return _label;
494 } else {
495 if (!_default_label) {
496 gchar const *id=SP_OBJECT_ID(this);
497 if (id) {
498 _default_label = g_strdup_printf("#%s", id);
499 } else {
500 _default_label = g_strdup_printf("<%s>", SP_OBJECT_REPR(this)->name());
501 }
502 }
503 return _default_label;
504 }
505 }
507 /** Sets the label property for the object */
508 void
509 SPObject::setLabel(gchar const *label) {
510 SP_OBJECT_REPR(this)->setAttribute("inkscape:label", label, false);
511 }
514 /** Queues the object for orphan collection */
515 void
516 SPObject::requestOrphanCollection() {
517 g_return_if_fail(document != NULL);
518 document->queueForOrphanCollection(this);
520 /** \todo
521 * This is a temporary hack added to make fill&stroke rebuild its
522 * gradient list when the defs are vacuumed. gradient-vector.cpp
523 * listens to the modified signal on defs, and now we give it that
524 * signal. Mental says that this should be made automatic by
525 * merging SPObjectGroup with SPObject; SPObjectGroup would issue
526 * this signal automatically. Or maybe just derive SPDefs from
527 * SPObjectGroup?
528 */
530 this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
531 }
533 /** Sends the delete signal to all children of this object recursively */
534 void
535 SPObject::_sendDeleteSignalRecursive() {
536 for (SPObject *child = sp_object_first_child(this); child; child = SP_OBJECT_NEXT(child)) {
537 child->_delete_signal.emit(child);
538 child->_sendDeleteSignalRecursive();
539 }
540 }
542 /**
543 * Deletes the object reference, unparenting it from its parent.
544 *
545 * If the \a propagate parameter is set to true, it emits a delete
546 * signal. If the \a propagate_descendants parameter is true, it
547 * recursively sends the delete signal to children.
548 */
549 void
550 SPObject::deleteObject(bool propagate, bool propagate_descendants)
551 {
552 sp_object_ref(this, NULL);
553 if (propagate) {
554 _delete_signal.emit(this);
555 }
556 if (propagate_descendants) {
557 this->_sendDeleteSignalRecursive();
558 }
560 Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
561 if (repr && sp_repr_parent(repr)) {
562 sp_repr_unparent(repr);
563 }
565 if (_successor) {
566 _successor->deleteObject(propagate, propagate_descendants);
567 }
568 sp_object_unref(this, NULL);
569 }
571 /**
572 * Put object into object tree, under parent, and behind prev;
573 * also update object's XML space.
574 */
575 void
576 sp_object_attach(SPObject *parent, SPObject *object, SPObject *prev)
577 {
578 g_return_if_fail(parent != NULL);
579 g_return_if_fail(SP_IS_OBJECT(parent));
580 g_return_if_fail(object != NULL);
581 g_return_if_fail(SP_IS_OBJECT(object));
582 g_return_if_fail(!prev || SP_IS_OBJECT(prev));
583 g_return_if_fail(!prev || prev->parent == parent);
584 g_return_if_fail(!object->parent);
586 sp_object_ref(object, parent);
587 object->parent = parent;
588 parent->_updateTotalHRefCount(object->_total_hrefcount);
590 SPObject *next;
591 if (prev) {
592 next = prev->next;
593 prev->next = object;
594 } else {
595 next = parent->children;
596 parent->children = object;
597 }
598 object->next = next;
599 if (!next) {
600 parent->_last_child = object;
601 }
602 if (!object->xml_space.set)
603 object->xml_space.value = parent->xml_space.value;
604 }
606 /**
607 * In list of object's siblings, move object behind prev.
608 */
609 void
610 sp_object_reorder(SPObject *object, SPObject *prev) {
611 g_return_if_fail(object != NULL);
612 g_return_if_fail(SP_IS_OBJECT(object));
613 g_return_if_fail(object->parent != NULL);
614 g_return_if_fail(object != prev);
615 g_return_if_fail(!prev || SP_IS_OBJECT(prev));
616 g_return_if_fail(!prev || prev->parent == object->parent);
618 SPObject *const parent=object->parent;
620 SPObject *old_prev=NULL;
621 for ( SPObject *child = parent->children ; child && child != object ;
622 child = child->next )
623 {
624 old_prev = child;
625 }
627 SPObject *next=object->next;
628 if (old_prev) {
629 old_prev->next = next;
630 } else {
631 parent->children = next;
632 }
633 if (!next) {
634 parent->_last_child = old_prev;
635 }
636 if (prev) {
637 next = prev->next;
638 prev->next = object;
639 } else {
640 next = parent->children;
641 parent->children = object;
642 }
643 object->next = next;
644 if (!next) {
645 parent->_last_child = object;
646 }
647 }
649 /**
650 * Remove object from parent's children, release and unref it.
651 */
652 void
653 sp_object_detach(SPObject *parent, SPObject *object) {
654 g_return_if_fail(parent != NULL);
655 g_return_if_fail(SP_IS_OBJECT(parent));
656 g_return_if_fail(object != NULL);
657 g_return_if_fail(SP_IS_OBJECT(object));
658 g_return_if_fail(object->parent == parent);
660 SPObject *prev=NULL;
661 for ( SPObject *child = parent->children ; child && child != object ;
662 child = child->next )
663 {
664 prev = child;
665 }
667 SPObject *next=object->next;
668 if (prev) {
669 prev->next = next;
670 } else {
671 parent->children = next;
672 }
673 if (!next) {
674 parent->_last_child = prev;
675 }
677 object->next = NULL;
678 object->parent = NULL;
680 sp_object_invoke_release(object);
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);
758 sp_object_detach(object, ochild);
759 }
761 /**
762 * Move object corresponding to child after sibling object corresponding
763 * to new_ref.
764 * Invoked whenever the given mutation event happens in the XML tree.
765 * \param old_ref Ignored
766 */
767 static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref,
768 Inkscape::XML::Node *new_ref)
769 {
770 SPObject *ochild = sp_object_get_child_by_repr(object, child);
771 g_return_if_fail(ochild != NULL);
772 SPObject *prev = new_ref ? sp_object_get_child_by_repr(object, new_ref) : NULL;
773 sp_object_reorder(ochild, prev);
774 }
776 /**
777 * Virtual build callback.
778 *
779 * This has to be invoked immediately after creation of an SPObject. The
780 * frontend method ensures that the new object is properly attached to
781 * the document and repr; implementation then will parse all of the attributes,
782 * generate the children objects and so on. Invoking build on the SPRoot
783 * object results in creation of the whole document tree (this is, what
784 * SPDocument does after the creation of the XML tree).
785 * \see sp_object_release()
786 */
787 static void
788 sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
789 {
790 /* Nothing specific here */
791 debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
793 sp_object_read_attr(object, "xml:space");
794 sp_object_read_attr(object, "inkscape:label");
795 sp_object_read_attr(object, "inkscape:collect");
797 for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != NULL; rchild = rchild->next()) {
798 GType type = sp_repr_type_lookup(rchild);
799 if (!type) {
800 continue;
801 }
802 SPObject *child = SP_OBJECT(g_object_new(type, 0));
803 sp_object_attach(object, child, object->lastChild());
804 sp_object_unref(child, NULL);
805 sp_object_invoke_build(child, document, rchild, SP_OBJECT_IS_CLONED(object));
806 }
807 }
809 void
810 sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
811 {
812 debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
814 g_assert(object != NULL);
815 g_assert(SP_IS_OBJECT(object));
816 g_assert(document != NULL);
817 g_assert(repr != NULL);
819 g_assert(object->document == NULL);
820 g_assert(object->repr == NULL);
821 g_assert(object->id == NULL);
823 /* Bookkeeping */
825 object->document = document;
826 object->repr = repr;
827 Inkscape::GC::anchor(repr);
828 object->cloned = cloned;
830 if (!SP_OBJECT_IS_CLONED(object)) {
831 object->document->bindObjectToRepr(object->repr, object);
833 if (Inkscape::XML::id_permitted(object->repr)) {
834 /* If we are not cloned, force unique id */
835 gchar const *id = object->repr->attribute("id");
836 gchar *realid = sp_object_get_unique_id(object, id);
837 g_assert(realid != NULL);
839 object->document->bindObjectToId(realid, object);
840 object->id = realid;
842 /* Redefine ID, if required */
843 if ((id == NULL) || (strcmp(id, realid) != 0)) {
844 gboolean undo_sensitive=sp_document_get_undo_sensitive(document);
845 sp_document_set_undo_sensitive(document, FALSE);
846 object->repr->setAttribute("id", realid);
847 sp_document_set_undo_sensitive(document, undo_sensitive);
848 }
849 }
850 } else {
851 g_assert(object->id == NULL);
852 }
854 /* Invoke derived methods, if any */
856 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build) {
857 (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build)(object, document, repr);
858 }
860 /* Signalling (should be connected AFTER processing derived methods */
861 sp_repr_add_listener(repr, &object_event_vector, object);
862 }
864 void
865 sp_object_invoke_release(SPObject *object)
866 {
867 g_assert(object != NULL);
868 g_assert(SP_IS_OBJECT(object));
870 // we need to remember our parent
871 // g_assert(!object->parent);
872 g_assert(!object->next);
873 g_assert(object->document);
874 g_assert(object->repr);
876 sp_repr_remove_listener_by_data(object->repr, object);
878 g_signal_emit(G_OBJECT(object), object_signals[RELEASE], 0);
880 /* all hrefs should be released by the "release" handlers */
881 g_assert(object->hrefcount == 0);
883 if (!SP_OBJECT_IS_CLONED(object)) {
884 if (object->id) {
885 object->document->bindObjectToId(object->id, NULL);
886 }
887 g_free(object->id);
888 object->id = NULL;
890 g_free(object->_default_label);
891 object->_default_label = NULL;
893 object->document->bindObjectToRepr(object->repr, NULL);
894 } else {
895 g_assert(!object->id);
896 }
898 if (object->style) {
899 object->style = sp_style_unref(object->style);
900 }
902 Inkscape::GC::release(object->repr);
904 object->document = NULL;
905 object->repr = NULL;
906 }
908 /**
909 * Callback for child_added node event.
910 */
911 static void
912 sp_object_repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data)
913 {
914 SPObject *object = SP_OBJECT(data);
916 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->child_added)
917 (*((SPObjectClass *)G_OBJECT_GET_CLASS(object))->child_added)(object, child, ref);
918 }
920 /**
921 * Callback for remove_child node event.
922 */
923 static void
924 sp_object_repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data)
925 {
926 SPObject *object = SP_OBJECT(data);
928 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->remove_child) {
929 (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->remove_child)(object, child);
930 }
931 }
933 /**
934 * Callback for order_changed node event.
935 *
936 * \todo fixme:
937 */
938 static void
939 sp_object_repr_order_changed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data)
940 {
941 SPObject *object = SP_OBJECT(data);
943 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->order_changed) {
944 (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->order_changed)(object, child, old, newer);
945 }
946 }
948 /**
949 * Callback for set event.
950 */
951 static void
952 sp_object_private_set(SPObject *object, unsigned int key, gchar const *value)
953 {
954 g_assert(key != SP_ATTR_INVALID);
956 switch (key) {
957 case SP_ATTR_ID:
958 if ( !SP_OBJECT_IS_CLONED(object) && object->repr->type() == Inkscape::XML::ELEMENT_NODE ) {
959 SPDocument *document=object->document;
960 SPObject *conflict=NULL;
962 if (value) {
963 conflict = document->getObjectById((char const *)value);
964 }
965 if ( conflict && conflict != object ) {
966 sp_object_ref(conflict, NULL);
967 // give the conflicting object a new ID
968 gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL);
969 SP_OBJECT_REPR(conflict)->setAttribute("id", new_conflict_id);
970 g_free(new_conflict_id);
971 sp_object_unref(conflict, NULL);
972 }
974 if (object->id) {
975 document->bindObjectToId(object->id, NULL);
976 g_free(object->id);
977 }
979 if (value) {
980 object->id = g_strdup((char const*)value);
981 document->bindObjectToId(object->id, object);
982 } else {
983 object->id = NULL;
984 }
986 g_free(object->_default_label);
987 object->_default_label = NULL;
988 }
989 break;
990 case SP_ATTR_INKSCAPE_LABEL:
991 g_free(object->_label);
992 if (value) {
993 object->_label = g_strdup(value);
994 } else {
995 object->_label = NULL;
996 }
997 g_free(object->_default_label);
998 object->_default_label = NULL;
999 break;
1000 case SP_ATTR_INKSCAPE_COLLECT:
1001 if ( value && !strcmp(value, "always") ) {
1002 object->setCollectionPolicy(SPObject::ALWAYS_COLLECT);
1003 } else {
1004 object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT);
1005 }
1006 break;
1007 case SP_ATTR_XML_SPACE:
1008 if (value && !strcmp(value, "preserve")) {
1009 object->xml_space.value = SP_XML_SPACE_PRESERVE;
1010 object->xml_space.set = TRUE;
1011 } else if (value && !strcmp(value, "default")) {
1012 object->xml_space.value = SP_XML_SPACE_DEFAULT;
1013 object->xml_space.set = TRUE;
1014 } else if (object->parent) {
1015 SPObject *parent;
1016 parent = object->parent;
1017 object->xml_space.value = parent->xml_space.value;
1018 }
1019 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1020 break;
1021 default:
1022 break;
1023 }
1024 }
1026 /**
1027 * Call virtual set() function of object.
1028 */
1029 void
1030 sp_object_set(SPObject *object, unsigned int key, gchar const *value)
1031 {
1032 g_assert(object != NULL);
1033 g_assert(SP_IS_OBJECT(object));
1035 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set) {
1036 ((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set(object, key, value);
1037 }
1038 }
1040 /**
1041 * Read value of key attribute from XML node into object.
1042 */
1043 void
1044 sp_object_read_attr(SPObject *object, gchar const *key)
1045 {
1046 g_assert(object != NULL);
1047 g_assert(SP_IS_OBJECT(object));
1048 g_assert(key != NULL);
1050 g_assert(object->repr != NULL);
1052 unsigned int keyid = sp_attribute_lookup(key);
1053 if (keyid != SP_ATTR_INVALID) {
1054 /* Retrieve the 'key' attribute from the object's XML representation */
1055 gchar const *value = object->repr->attribute(key);
1057 sp_object_set(object, keyid, value);
1058 }
1059 }
1061 /**
1062 * Callback for attr_changed node event.
1063 */
1064 static void
1065 sp_object_repr_attr_changed(Inkscape::XML::Node *repr, gchar const *key, gchar const *oldval, gchar const *newval, bool is_interactive, gpointer data)
1066 {
1067 SPObject *object = SP_OBJECT(data);
1069 sp_object_read_attr(object, key);
1071 // manual changes to extension attributes require the normal
1072 // attributes, which depend on them, to be updated immediately
1073 if (is_interactive) {
1074 object->updateRepr(repr, 0);
1075 }
1076 }
1078 /**
1079 * Callback for content_changed node event.
1080 */
1081 static void
1082 sp_object_repr_content_changed(Inkscape::XML::Node *repr, gchar const *oldcontent, gchar const *newcontent, gpointer data)
1083 {
1084 SPObject *object = SP_OBJECT(data);
1086 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)
1087 (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)(object);
1088 }
1090 /**
1091 * Return string representation of space value.
1092 */
1093 static gchar const*
1094 sp_xml_get_space_string(unsigned int space)
1095 {
1096 switch (space) {
1097 case SP_XML_SPACE_DEFAULT:
1098 return "default";
1099 case SP_XML_SPACE_PRESERVE:
1100 return "preserve";
1101 default:
1102 return NULL;
1103 }
1104 }
1106 /**
1107 * Callback for write event.
1108 */
1109 static Inkscape::XML::Node *
1110 sp_object_private_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
1111 {
1112 if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) {
1113 repr = SP_OBJECT_REPR(object)->duplicate();
1114 if (!( flags & SP_OBJECT_WRITE_EXT )) {
1115 repr->setAttribute("inkscape:collect", NULL);
1116 }
1117 } else {
1118 repr->setAttribute("id", object->id);
1120 if (object->xml_space.set) {
1121 char const *xml_space;
1122 xml_space = sp_xml_get_space_string(object->xml_space.value);
1123 repr->setAttribute("xml:space", xml_space);
1124 }
1126 if ( flags & SP_OBJECT_WRITE_EXT &&
1127 object->collectionPolicy() == SPObject::ALWAYS_COLLECT )
1128 {
1129 repr->setAttribute("inkscape:collect", "always");
1130 } else {
1131 repr->setAttribute("inkscape:collect", NULL);
1132 }
1133 }
1135 return repr;
1136 }
1138 /**
1139 * Update this object's XML node with flags value.
1140 */
1141 Inkscape::XML::Node *
1142 SPObject::updateRepr(unsigned int flags) {
1143 if (!SP_OBJECT_IS_CLONED(this)) {
1144 Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
1145 if (repr) {
1146 return updateRepr(repr, flags);
1147 } else {
1148 g_critical("Attempt to update non-existent repr");
1149 return NULL;
1150 }
1151 } else {
1152 /* cloned objects have no repr */
1153 return NULL;
1154 }
1155 }
1157 Inkscape::XML::Node *
1158 SPObject::updateRepr(Inkscape::XML::Node *repr, unsigned int flags) {
1159 if (SP_OBJECT_IS_CLONED(this)) {
1160 /* cloned objects have no repr */
1161 return NULL;
1162 }
1163 if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write) {
1164 if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1165 repr = SP_OBJECT_REPR(this);
1166 }
1167 return ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write(this, repr, flags);
1168 } else {
1169 g_warning("Class %s does not implement ::write", G_OBJECT_TYPE_NAME(this));
1170 if (!repr) {
1171 if (flags & SP_OBJECT_WRITE_BUILD) {
1172 repr = SP_OBJECT_REPR(this)->duplicate();
1173 }
1174 /// \todo fixme: else probably error (Lauris) */
1175 } else {
1176 repr->mergeFrom(SP_OBJECT_REPR(this), "id");
1177 }
1178 return repr;
1179 }
1180 }
1182 /* Modification */
1184 /**
1185 * Add \a flags to \a object's as dirtiness flags, and
1186 * recursively add CHILD_MODIFIED flag to
1187 * parent and ancestors (as far up as necessary).
1188 */
1189 void
1190 SPObject::requestDisplayUpdate(unsigned int flags)
1191 {
1192 if (update_in_progress) {
1193 g_print("WARNING: Requested update while update in progress, counter = %d\n", update_in_progress);
1194 }
1196 g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1197 g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1198 g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1200 /* Check for propagate before we set any flags */
1201 /* Propagate means, that this is not passed through by modification request cascade yet */
1202 unsigned int propagate = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1204 /* Just set this flags safe even if some have been set before */
1205 this->uflags |= flags;
1207 if (propagate) {
1208 if (this->parent) {
1209 this->parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
1210 } else {
1211 sp_document_request_modified(this->document);
1212 }
1213 }
1214 }
1216 void
1217 SPObject::updateDisplay(SPCtx *ctx, unsigned int flags)
1218 {
1219 g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1221 update_in_progress ++;
1223 #ifdef SP_OBJECT_DEBUG_CASCADE
1224 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);
1225 #endif
1227 /* Get this flags */
1228 flags |= this->uflags;
1229 /* Copy flags to modified cascade for later processing */
1230 this->mflags |= this->uflags;
1231 /* We have to clear flags here to allow rescheduling update */
1232 this->uflags = 0;
1234 // Merge style if we have good reasons to think that parent style is changed */
1235 /** \todo
1236 * I am not sure whether we should check only propagated
1237 * flag. We are currently assuming that style parsing is
1238 * done immediately. I think this is correct (Lauris).
1239 */
1240 if ((flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1241 if (this->style && this->parent) {
1242 sp_style_merge_from_parent(this->style, this->parent->style);
1243 }
1244 }
1246 if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update)
1247 ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);
1249 update_in_progress --;
1250 }
1252 void
1253 SPObject::requestModified(unsigned int flags)
1254 {
1255 g_return_if_fail( this->document != NULL );
1257 /* PARENT_MODIFIED is computed later on and is not intended to be
1258 * "manually" queued */
1259 g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1261 /* we should be setting either MODIFIED or CHILD_MODIFIED... */
1262 g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1264 /* ...but not both */
1265 g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1267 unsigned int old_mflags=this->mflags;
1268 this->mflags |= flags;
1270 /* If we already had MODIFIED or CHILD_MODIFIED queued, we will
1271 * have already queued CHILD_MODIFIED with our ancestors and
1272 * need not disturb them again.
1273 */
1274 if (!( old_mflags & ( SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG ) )) {
1275 SPObject *parent=SP_OBJECT_PARENT(this);
1276 if (parent) {
1277 parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
1278 } else {
1279 sp_document_request_modified(SP_OBJECT_DOCUMENT(this));
1280 }
1281 }
1282 }
1284 void
1285 SPObject::emitModified(unsigned int flags)
1286 {
1287 /* only the MODIFIED_CASCADE flag is legal here */
1288 g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1290 #ifdef SP_OBJECT_DEBUG_CASCADE
1291 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);
1292 #endif
1294 flags |= this->mflags;
1295 /* We have to clear mflags beforehand, as signal handlers may
1296 * make changes and therefore queue new modification notifications
1297 * themselves. */
1298 this->mflags = 0;
1300 g_object_ref(G_OBJECT(this));
1301 g_signal_emit(G_OBJECT(this), object_signals[MODIFIED], 0, flags);
1302 g_object_unref(G_OBJECT(this));
1303 }
1305 /*
1306 * Get and set descriptive parameters
1307 *
1308 * These are inefficent, so they are not intended to be used interactively
1309 */
1311 gchar const *
1312 sp_object_title_get(SPObject *object)
1313 {
1314 return NULL;
1315 }
1317 gchar const *
1318 sp_object_description_get(SPObject *object)
1319 {
1320 return NULL;
1321 }
1323 unsigned int
1324 sp_object_title_set(SPObject *object, gchar const *title)
1325 {
1326 return FALSE;
1327 }
1329 unsigned int
1330 sp_object_description_set(SPObject *object, gchar const *desc)
1331 {
1332 return FALSE;
1333 }
1335 gchar const *
1336 sp_object_tagName_get(SPObject const *object, SPException *ex)
1337 {
1338 /* If exception is not clear, return */
1339 if (!SP_EXCEPTION_IS_OK(ex)) {
1340 return NULL;
1341 }
1343 /// \todo fixme: Exception if object is NULL? */
1344 return object->repr->name();
1345 }
1347 gchar const *
1348 sp_object_getAttribute(SPObject const *object, gchar const *key, SPException *ex)
1349 {
1350 /* If exception is not clear, return */
1351 if (!SP_EXCEPTION_IS_OK(ex)) {
1352 return NULL;
1353 }
1355 /// \todo fixme: Exception if object is NULL? */
1356 return (gchar const *) object->repr->attribute(key);
1357 }
1359 void
1360 sp_object_setAttribute(SPObject *object, gchar const *key, gchar const *value, SPException *ex)
1361 {
1362 /* If exception is not clear, return */
1363 g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1365 /// \todo fixme: Exception if object is NULL? */
1366 if (!sp_repr_set_attr(object->repr, key, value)) {
1367 ex->code = SP_NO_MODIFICATION_ALLOWED_ERR;
1368 }
1369 }
1371 void
1372 sp_object_removeAttribute(SPObject *object, gchar const *key, SPException *ex)
1373 {
1374 /* If exception is not clear, return */
1375 g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1377 /// \todo fixme: Exception if object is NULL? */
1378 if (!sp_repr_set_attr(object->repr, key, NULL)) {
1379 ex->code = SP_NO_MODIFICATION_ALLOWED_ERR;
1380 }
1381 }
1383 /* Helper */
1385 static gchar *
1386 sp_object_get_unique_id(SPObject *object, gchar const *id)
1387 {
1388 static unsigned long count = 0;
1390 g_assert(SP_IS_OBJECT(object));
1392 count++;
1394 gchar const *name = object->repr->name();
1395 g_assert(name != NULL);
1397 gchar const *local = strchr(name, ':');
1398 if (local) {
1399 name = local + 1;
1400 }
1402 if (id != NULL) {
1403 if (object->document->getObjectById(id) == NULL) {
1404 return g_strdup(id);
1405 }
1406 }
1408 size_t const name_len = strlen(name);
1409 size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1;
1410 gchar *const buf = (gchar *) g_malloc(buflen);
1411 memcpy(buf, name, name_len);
1412 gchar *const count_buf = buf + name_len;
1413 size_t const count_buflen = buflen - name_len;
1414 do {
1415 ++count;
1416 g_snprintf(count_buf, count_buflen, "%lu", count);
1417 } while ( object->document->getObjectById(buf) != NULL );
1418 return buf;
1419 }
1421 /* Style */
1423 /**
1424 * Returns an object style property.
1425 *
1426 * \todo
1427 * fixme: Use proper CSS parsing. The current version is buggy
1428 * in a number of situations where key is a substring of the
1429 * style string other than as a property name (including
1430 * where key is a substring of a property name), and is also
1431 * buggy in its handling of inheritance for properties that
1432 * aren't inherited by default. It also doesn't allow for
1433 * the case where the property is specified but with an invalid
1434 * value (in which case I believe the CSS2 error-handling
1435 * behaviour applies, viz. behave as if the property hadn't
1436 * been specified). Also, the current code doesn't use CRSelEng
1437 * stuff to take a value from stylesheets. Also, we aren't
1438 * setting any hooks to force an update for changes in any of
1439 * the inputs (i.e., in any of the elements that this function
1440 * queries).
1441 *
1442 * \par
1443 * Given that the default value for a property depends on what
1444 * property it is (e.g., whether to inherit or not), and given
1445 * the above comment about ignoring invalid values, and that the
1446 * repr parent isn't necessarily the right element to inherit
1447 * from (e.g., maybe we need to inherit from the referencing
1448 * <use> element instead), we should probably make the caller
1449 * responsible for ascending the repr tree as necessary.
1450 */
1451 gchar const *
1452 sp_object_get_style_property(SPObject const *object, gchar const *key, gchar const *def)
1453 {
1454 g_return_val_if_fail(object != NULL, NULL);
1455 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
1456 g_return_val_if_fail(key != NULL, NULL);
1458 gchar const *style = object->repr->attribute("style");
1459 if (style) {
1460 size_t const len = strlen(key);
1461 char const *p;
1462 while ( (p = strstr(style, key))
1463 != NULL )
1464 {
1465 p += len;
1466 while ((*p <= ' ') && *p) p++;
1467 if (*p++ != ':') break;
1468 while ((*p <= ' ') && *p) p++;
1469 size_t const inherit_len = sizeof("inherit") - 1;
1470 if (*p
1471 && !(strneq(p, "inherit", inherit_len)
1472 && (p[inherit_len] == '\0'
1473 || p[inherit_len] == ';'
1474 || g_ascii_isspace(p[inherit_len])))) {
1475 return p;
1476 }
1477 }
1478 }
1479 gchar const *val = object->repr->attribute(key);
1480 if (val && !streq(val, "inherit")) {
1481 return val;
1482 }
1483 if (object->parent) {
1484 return sp_object_get_style_property(object->parent, key, def);
1485 }
1487 return def;
1488 }
1490 /**
1491 * Lifts SVG version of all root objects to version.
1492 */
1493 void
1494 SPObject::_requireSVGVersion(Inkscape::Version version) {
1495 for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) {
1496 SPObject *object=iter;
1497 if (SP_IS_ROOT(object)) {
1498 SPRoot *root=SP_ROOT(object);
1499 if ( root->version.svg < version ) {
1500 root->version.svg = version;
1501 }
1502 }
1503 }
1504 }
1506 /**
1507 * Return sodipodi version of first root ancestor or (0,0).
1508 */
1509 Inkscape::Version
1510 sp_object_get_sodipodi_version(SPObject *object)
1511 {
1512 static Inkscape::Version const zero_version(0, 0);
1514 while (object) {
1515 if (SP_IS_ROOT(object)) {
1516 return SP_ROOT(object)->version.sodipodi;
1517 }
1518 object = SP_OBJECT_PARENT(object);
1519 }
1521 return zero_version;
1522 }
1524 /**
1525 * Returns previous object in sibling list or NULL.
1526 */
1527 SPObject *
1528 sp_object_prev(SPObject *child)
1529 {
1530 SPObject *parent = SP_OBJECT_PARENT(child);
1531 for ( SPObject *i = sp_object_first_child(parent); i; i = SP_OBJECT_NEXT(i) ) {
1532 if (SP_OBJECT_NEXT(i) == child)
1533 return i;
1534 }
1535 return NULL;
1536 }
1539 /*
1540 Local Variables:
1541 mode:c++
1542 c-file-style:"stroustrup"
1543 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1544 indent-tabs-mode:nil
1545 fill-column:99
1546 End:
1547 */
1548 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :