f81d5bdb59897b5d5a1ce18c452d5b267367ea5c
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"
56 #include "util/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 // A friend class used to set internal members on SPObject so as to not expose settors in SPObject's public API
116 class SPObjectImpl
117 {
118 public:
120 /**
121 * Null's the id member of an SPObject without attempting to free prior contents.
122 */
123 static void setIdNull( SPObject* obj ) {
124 if (obj) {
125 obj->id = 0;
126 }
127 }
129 /**
130 * Sets the id member of an object, freeing any prior content.
131 */
132 static void setId( SPObject* obj, gchar const* id ) {
133 if (obj && (id != obj->id) ) {
134 if (obj->id) {
135 g_free(obj->id);
136 obj->id = 0;
137 }
138 if (id) {
139 obj->id = g_strdup(id);
140 }
141 }
142 }
143 };
146 static GObjectClass *parent_class;
148 /**
149 * Registers the SPObject class with Gdk and returns its type number.
150 */
151 GType
152 sp_object_get_type(void)
153 {
154 static GType type = 0;
155 if (!type) {
156 GTypeInfo info = {
157 sizeof(SPObjectClass),
158 NULL, NULL,
159 (GClassInitFunc) sp_object_class_init,
160 NULL, NULL,
161 sizeof(SPObject),
162 16,
163 (GInstanceInitFunc) sp_object_init,
164 NULL
165 };
166 type = g_type_register_static(G_TYPE_OBJECT, "SPObject", &info, (GTypeFlags)0);
167 }
168 return type;
169 }
171 /**
172 * Initializes the SPObject vtable.
173 */
174 static void
175 sp_object_class_init(SPObjectClass *klass)
176 {
177 GObjectClass *object_class;
179 object_class = (GObjectClass *) klass;
181 parent_class = (GObjectClass *) g_type_class_ref(G_TYPE_OBJECT);
183 object_class->finalize = sp_object_finalize;
185 klass->child_added = sp_object_child_added;
186 klass->remove_child = sp_object_remove_child;
187 klass->order_changed = sp_object_order_changed;
189 klass->release = sp_object_release;
191 klass->build = sp_object_build;
193 klass->set = sp_object_private_set;
194 klass->write = sp_object_private_write;
195 }
197 /**
198 * Callback to initialize the SPObject object.
199 */
200 static void
201 sp_object_init(SPObject *object)
202 {
203 debug("id=%x, typename=%s",object, g_type_name_from_instance((GTypeInstance*)object));
205 object->hrefcount = 0;
206 object->_total_hrefcount = 0;
207 object->document = NULL;
208 object->children = object->_last_child = NULL;
209 object->parent = object->next = NULL;
211 //used XML Tree here.
212 Inkscape::XML::Node *repr = object->getRepr();
213 repr = NULL;
214 SPObjectImpl::setIdNull(object);
216 object->_collection_policy = SPObject::COLLECT_WITH_PARENT;
218 new (&object->_release_signal) sigc::signal<void, SPObject *>();
219 new (&object->_modified_signal) sigc::signal<void, SPObject *, unsigned int>();
220 new (&object->_delete_signal) sigc::signal<void, SPObject *>();
221 new (&object->_position_changed_signal) sigc::signal<void, SPObject *>();
222 object->_successor = NULL;
224 // FIXME: now we create style for all objects, but per SVG, only the following can have style attribute:
225 // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline,
226 // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient,
227 // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject
228 object->style = sp_style_new_from_object(object);
230 object->_label = NULL;
231 object->_default_label = NULL;
232 }
234 /**
235 * Callback to destroy all members and connections of object and itself.
236 */
237 static void
238 sp_object_finalize(GObject *object)
239 {
240 SPObject *spobject = (SPObject *)object;
242 g_free(spobject->_label);
243 g_free(spobject->_default_label);
244 spobject->_label = NULL;
245 spobject->_default_label = NULL;
247 if (spobject->_successor) {
248 sp_object_unref(spobject->_successor, NULL);
249 spobject->_successor = NULL;
250 }
252 if (((GObjectClass *) (parent_class))->finalize) {
253 (* ((GObjectClass *) (parent_class))->finalize)(object);
254 }
256 spobject->_release_signal.~signal();
257 spobject->_modified_signal.~signal();
258 spobject->_delete_signal.~signal();
259 spobject->_position_changed_signal.~signal();
260 }
262 namespace {
264 namespace Debug = Inkscape::Debug;
265 namespace Util = Inkscape::Util;
267 typedef Debug::SimpleEvent<Debug::Event::REFCOUNT> BaseRefCountEvent;
269 class RefCountEvent : public BaseRefCountEvent {
270 public:
271 RefCountEvent(SPObject *object, int bias, Util::ptr_shared<char> name)
272 : BaseRefCountEvent(name)
273 {
274 _addProperty("object", Util::format("%p", object));
275 _addProperty("class", Debug::demangle(g_type_name(G_TYPE_FROM_INSTANCE(object))));
276 _addProperty("new-refcount", Util::format("%d", G_OBJECT(object)->ref_count + bias));
277 }
278 };
280 class RefEvent : public RefCountEvent {
281 public:
282 RefEvent(SPObject *object)
283 : RefCountEvent(object, 1, Util::share_static_string("sp-object-ref"))
284 {}
285 };
287 class UnrefEvent : public RefCountEvent {
288 public:
289 UnrefEvent(SPObject *object)
290 : RefCountEvent(object, -1, Util::share_static_string("sp-object-unref"))
291 {}
292 };
294 }
296 gchar const* SPObject::getId() const {
297 return id;
298 }
300 Inkscape::XML::Node * SPObject::getRepr() {
301 return repr;
302 }
304 Inkscape::XML::Node const* SPObject::getRepr() const{
305 return repr;
306 }
309 /**
310 * Increase reference count of object, with possible debugging.
311 *
312 * \param owner If non-NULL, make debug log entry.
313 * \return object, NULL is error.
314 * \pre object points to real object
315 */
316 SPObject *
317 sp_object_ref(SPObject *object, SPObject *owner)
318 {
319 g_return_val_if_fail(object != NULL, NULL);
320 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
321 g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
323 Inkscape::Debug::EventTracker<RefEvent> tracker(object);
324 g_object_ref(G_OBJECT(object));
325 return object;
326 }
328 /**
329 * Decrease reference count of object, with possible debugging and
330 * finalization.
331 *
332 * \param owner If non-NULL, make debug log entry.
333 * \return always NULL
334 * \pre object points to real object
335 */
336 SPObject *
337 sp_object_unref(SPObject *object, SPObject *owner)
338 {
339 g_return_val_if_fail(object != NULL, NULL);
340 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
341 g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL);
343 Inkscape::Debug::EventTracker<UnrefEvent> tracker(object);
344 g_object_unref(G_OBJECT(object));
345 return NULL;
346 }
348 /**
349 * Increase weak refcount.
350 *
351 * Hrefcount is used for weak references, for example, to
352 * determine whether any graphical element references a certain gradient
353 * node.
354 * \param owner Ignored.
355 * \return object, NULL is error
356 * \pre object points to real object
357 */
358 SPObject *
359 sp_object_href(SPObject *object, gpointer /*owner*/)
360 {
361 g_return_val_if_fail(object != NULL, NULL);
362 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
364 object->hrefcount++;
365 object->_updateTotalHRefCount(1);
367 return object;
368 }
370 /**
371 * Decrease weak refcount.
372 *
373 * Hrefcount is used for weak references, for example, to determine whether
374 * any graphical element references a certain gradient node.
375 * \param owner Ignored.
376 * \return always NULL
377 * \pre object points to real object and hrefcount>0
378 */
379 SPObject *
380 sp_object_hunref(SPObject *object, gpointer /*owner*/)
381 {
382 g_return_val_if_fail(object != NULL, NULL);
383 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
384 g_return_val_if_fail(object->hrefcount > 0, NULL);
386 object->hrefcount--;
387 object->_updateTotalHRefCount(-1);
389 return NULL;
390 }
392 /**
393 * Adds increment to _total_hrefcount of object and its parents.
394 */
395 void
396 SPObject::_updateTotalHRefCount(int increment) {
397 SPObject *topmost_collectable = NULL;
398 for ( SPObject *iter = this ; iter ; iter = SP_OBJECT_PARENT(iter) ) {
399 iter->_total_hrefcount += increment;
400 if ( iter->_total_hrefcount < iter->hrefcount ) {
401 g_critical("HRefs overcounted");
402 }
403 if ( iter->_total_hrefcount == 0 &&
404 iter->_collection_policy != COLLECT_WITH_PARENT )
405 {
406 topmost_collectable = iter;
407 }
408 }
409 if (topmost_collectable) {
410 topmost_collectable->requestOrphanCollection();
411 }
412 }
414 /**
415 * True if object is non-NULL and this is some in/direct parent of object.
416 */
417 bool
418 SPObject::isAncestorOf(SPObject const *object) const {
419 g_return_val_if_fail(object != NULL, false);
420 object = SP_OBJECT_PARENT(object);
421 while (object) {
422 if ( object == this ) {
423 return true;
424 }
425 object = SP_OBJECT_PARENT(object);
426 }
427 return false;
428 }
430 namespace {
432 bool same_objects(SPObject const &a, SPObject const &b) {
433 return &a == &b;
434 }
436 }
438 /**
439 * Returns youngest object being parent to this and object.
440 */
441 SPObject const *
442 SPObject::nearestCommonAncestor(SPObject const *object) const {
443 g_return_val_if_fail(object != NULL, NULL);
445 using Inkscape::Algorithms::longest_common_suffix;
446 return longest_common_suffix<SPObject::ConstParentIterator>(this, object, NULL, &same_objects);
447 }
449 SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) {
450 if (obj == NULL || ancestor == NULL)
451 return NULL;
452 if (SP_OBJECT_PARENT(obj) == ancestor)
453 return obj;
454 return AncestorSon(SP_OBJECT_PARENT(obj), ancestor);
455 }
457 /**
458 * Compares height of objects in tree.
459 *
460 * Works for different-parent objects, so long as they have a common ancestor.
461 * \return \verbatim
462 * 0 positions are equivalent
463 * 1 first object's position is greater than the second
464 * -1 first object's position is less than the second \endverbatim
465 */
466 int
467 sp_object_compare_position(SPObject const *first, SPObject const *second)
468 {
469 if (first == second) return 0;
471 SPObject const *ancestor = first->nearestCommonAncestor(second);
472 if (ancestor == NULL) return 0; // cannot compare, no common ancestor!
474 // we have an object and its ancestor (should not happen when sorting selection)
475 if (ancestor == first)
476 return 1;
477 if (ancestor == second)
478 return -1;
480 SPObject const *to_first = AncestorSon(first, ancestor);
481 SPObject const *to_second = AncestorSon(second, ancestor);
483 g_assert(SP_OBJECT_PARENT(to_second) == SP_OBJECT_PARENT(to_first));
485 return sp_repr_compare_position(SP_OBJECT_REPR(to_first), SP_OBJECT_REPR(to_second));
486 }
489 /**
490 * Append repr as child of this object.
491 * \pre this is not a cloned object
492 */
493 SPObject *
494 SPObject::appendChildRepr(Inkscape::XML::Node *repr) {
495 if (!SP_OBJECT_IS_CLONED(this)) {
496 SP_OBJECT_REPR(this)->appendChild(repr);
497 return SP_OBJECT_DOCUMENT(this)->getObjectByRepr(repr);
498 } else {
499 g_critical("Attempt to append repr as child of cloned object");
500 return NULL;
501 }
502 }
504 /**
505 * Retrieves the children as a GSList object, optionally ref'ing the children
506 * in the process, if add_ref is specified.
507 */
508 GSList *SPObject::childList(bool add_ref, Action) {
509 GSList *l = NULL;
510 for (SPObject *child = this->first_child() ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
511 if (add_ref)
512 g_object_ref (G_OBJECT (child));
514 l = g_slist_prepend (l, child);
515 }
516 return l;
518 }
520 /** Gets the label property for the object or a default if no label
521 * is defined.
522 */
523 gchar const *
524 SPObject::label() const {
525 return _label;
526 }
528 /** Returns a default label property for the object. */
529 gchar const *
530 SPObject::defaultLabel() const {
531 if (_label) {
532 return _label;
533 } else {
534 if (!_default_label) {
535 gchar const *id=SP_OBJECT_ID(this);
536 if (id) {
537 _default_label = g_strdup_printf("#%s", id);
538 } else {
539 _default_label = g_strdup_printf("<%s>", SP_OBJECT_REPR(this)->name());
540 }
541 }
542 return _default_label;
543 }
544 }
546 /** Sets the label property for the object */
547 void
548 SPObject::setLabel(gchar const *label) {
549 SP_OBJECT_REPR(this)->setAttribute("inkscape:label", label, false);
550 }
553 /** Queues the object for orphan collection */
554 void
555 SPObject::requestOrphanCollection() {
556 g_return_if_fail(document != NULL);
558 // do not remove style or script elements (Bug #276244)
559 if (SP_IS_STYLE_ELEM(this))
560 return;
561 if (SP_IS_SCRIPT(this))
562 return;
564 document->queueForOrphanCollection(this);
566 /** \todo
567 * This is a temporary hack added to make fill&stroke rebuild its
568 * gradient list when the defs are vacuumed. gradient-vector.cpp
569 * listens to the modified signal on defs, and now we give it that
570 * signal. Mental says that this should be made automatic by
571 * merging SPObjectGroup with SPObject; SPObjectGroup would issue
572 * this signal automatically. Or maybe just derive SPDefs from
573 * SPObjectGroup?
574 */
576 this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
577 }
579 /** Sends the delete signal to all children of this object recursively */
580 void
581 SPObject::_sendDeleteSignalRecursive() {
582 for (SPObject *child = this->first_child(); child; child = SP_OBJECT_NEXT(child)) {
583 child->_delete_signal.emit(child);
584 child->_sendDeleteSignalRecursive();
585 }
586 }
588 /**
589 * Deletes the object reference, unparenting it from its parent.
590 *
591 * If the \a propagate parameter is set to true, it emits a delete
592 * signal. If the \a propagate_descendants parameter is true, it
593 * recursively sends the delete signal to children.
594 */
595 void
596 SPObject::deleteObject(bool propagate, bool propagate_descendants)
597 {
598 sp_object_ref(this, NULL);
599 if (propagate) {
600 _delete_signal.emit(this);
601 }
602 if (propagate_descendants) {
603 this->_sendDeleteSignalRecursive();
604 }
606 Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
607 if (repr && sp_repr_parent(repr)) {
608 sp_repr_unparent(repr);
609 }
611 if (_successor) {
612 _successor->deleteObject(propagate, propagate_descendants);
613 }
614 sp_object_unref(this, NULL);
615 }
617 /**
618 * Put object into object tree, under parent, and behind prev;
619 * also update object's XML space.
620 */
621 void
622 SPObject::attach(SPObject *object, SPObject *prev)
623 {
624 //g_return_if_fail(parent != NULL);
625 //g_return_if_fail(SP_IS_OBJECT(parent));
626 g_return_if_fail(object != NULL);
627 g_return_if_fail(SP_IS_OBJECT(object));
628 g_return_if_fail(!prev || SP_IS_OBJECT(prev));
629 g_return_if_fail(!prev || prev->parent == this);
630 g_return_if_fail(!object->parent);
632 sp_object_ref(object, this);
633 object->parent = this;
634 this->_updateTotalHRefCount(object->_total_hrefcount);
636 SPObject *next;
637 if (prev) {
638 next = prev->next;
639 prev->next = object;
640 } else {
641 next = this->children;
642 this->children = object;
643 }
644 object->next = next;
645 if (!next) {
646 this->_last_child = object;
647 }
648 if (!object->xml_space.set)
649 object->xml_space.value = this->xml_space.value;
650 }
652 /**
653 * In list of object's siblings, move object behind prev.
654 */
655 void
656 SPObject::reorder(SPObject *prev) {
657 //g_return_if_fail(object != NULL);
658 //g_return_if_fail(SP_IS_OBJECT(object));
659 g_return_if_fail(this->parent != NULL);
660 g_return_if_fail(this != prev);
661 g_return_if_fail(!prev || SP_IS_OBJECT(prev));
662 g_return_if_fail(!prev || prev->parent == this->parent);
664 SPObject *const parent=this->parent;
666 SPObject *old_prev=NULL;
667 for ( SPObject *child = parent->children ; child && child != this ;
668 child = child->next )
669 {
670 old_prev = child;
671 }
673 SPObject *next=this->next;
674 if (old_prev) {
675 old_prev->next = next;
676 } else {
677 parent->children = next;
678 }
679 if (!next) {
680 parent->_last_child = old_prev;
681 }
682 if (prev) {
683 next = prev->next;
684 prev->next = this;
685 } else {
686 next = parent->children;
687 parent->children = this;
688 }
689 this->next = next;
690 if (!next) {
691 parent->_last_child = this;
692 }
693 }
695 /**
696 * Remove object from parent's children, release and unref it.
697 */
698 void
699 SPObject::detach(SPObject *object) {
700 //g_return_if_fail(parent != NULL);
701 //g_return_if_fail(SP_IS_OBJECT(parent));
702 g_return_if_fail(object != NULL);
703 g_return_if_fail(SP_IS_OBJECT(object));
704 g_return_if_fail(object->parent == this);
706 object->releaseReferences();
708 SPObject *prev=NULL;
709 for ( SPObject *child = this->children ; child && child != object ;
710 child = child->next )
711 {
712 prev = child;
713 }
715 SPObject *next=object->next;
716 if (prev) {
717 prev->next = next;
718 } else {
719 this->children = next;
720 }
721 if (!next) {
722 this->_last_child = prev;
723 }
725 object->next = NULL;
726 object->parent = NULL;
728 this->_updateTotalHRefCount(-object->_total_hrefcount);
729 sp_object_unref(object, this);
730 }
732 /**
733 * Return object's child whose node pointer equals repr.
734 */
735 SPObject *
736 SPObject::get_child_by_repr(Inkscape::XML::Node *repr)
737 {
738 //g_return_val_if_fail(object != NULL, NULL);
739 //g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
740 g_return_val_if_fail(repr != NULL, NULL);
742 if (this->_last_child && SP_OBJECT_REPR(this->_last_child) == repr)
743 return this->_last_child; // optimization for common scenario
744 for ( SPObject *child = this->children ; child ; child = child->next ) {
745 if ( SP_OBJECT_REPR(child) == repr ) {
746 return child;
747 }
748 }
750 return NULL;
751 }
753 /**
754 * Callback for child_added event.
755 * Invoked whenever the given mutation event happens in the XML tree.
756 */
757 static void
758 sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref)
759 {
760 GType type = sp_repr_type_lookup(child);
761 if (!type) {
762 return;
763 }
764 SPObject *ochild = SP_OBJECT(g_object_new(type, 0));
765 SPObject *prev = ref ? object->get_child_by_repr(ref) : NULL;
766 object->attach(ochild, prev);
767 sp_object_unref(ochild, NULL);
769 ochild->invoke_build(object->document, child, SP_OBJECT_IS_CLONED(object));
770 }
772 /**
773 * Removes, releases and unrefs all children of object.
774 *
775 * This is the opposite of build. It has to be invoked as soon as the
776 * object is removed from the tree, even if it is still alive according
777 * to reference count. The frontend unregisters the object from the
778 * document and releases the SPRepr bindings; implementations should free
779 * state data and release all child objects. Invoking release on
780 * SPRoot destroys the whole document tree.
781 * \see sp_object_build()
782 */
783 static void sp_object_release(SPObject *object)
784 {
785 debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
786 while (object->children) {
787 object->detach(object->children);
788 }
789 }
791 /**
792 * Remove object's child whose node equals repr, release and
793 * unref it.
794 *
795 * Invoked whenever the given mutation event happens in the XML
796 * tree, BEFORE removal from the XML tree happens, so grouping
797 * objects can safely release the child data.
798 */
799 static void
800 sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child)
801 {
802 debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
803 SPObject *ochild = object->get_child_by_repr(child);
804 g_return_if_fail (ochild != NULL || !strcmp("comment", child->name())); // comments have no objects
805 if (ochild)
806 object->detach(ochild);
807 }
809 /**
810 * Move object corresponding to child after sibling object corresponding
811 * to new_ref.
812 * Invoked whenever the given mutation event happens in the XML tree.
813 * \param old_ref Ignored
814 */
815 static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node */*old_ref*/,
816 Inkscape::XML::Node *new_ref)
817 {
818 SPObject *ochild = object->get_child_by_repr(child);
819 g_return_if_fail(ochild != NULL);
820 SPObject *prev = new_ref ? object->get_child_by_repr(new_ref) : NULL;
821 ochild->reorder(prev);
822 ochild->_position_changed_signal.emit(ochild);
823 }
825 /**
826 * Virtual build callback.
827 *
828 * This has to be invoked immediately after creation of an SPObject. The
829 * frontend method ensures that the new object is properly attached to
830 * the document and repr; implementation then will parse all of the attributes,
831 * generate the children objects and so on. Invoking build on the SPRoot
832 * object results in creation of the whole document tree (this is, what
833 * SPDocument does after the creation of the XML tree).
834 * \see sp_object_release()
835 */
836 static void
837 sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
838 {
839 /* Nothing specific here */
840 debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object));
842 sp_object_read_attr(object, "xml:space");
843 sp_object_read_attr(object, "inkscape:label");
844 sp_object_read_attr(object, "inkscape:collect");
846 for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != NULL; rchild = rchild->next()) {
847 GType type = sp_repr_type_lookup(rchild);
848 if (!type) {
849 continue;
850 }
851 SPObject *child = SP_OBJECT(g_object_new(type, 0));
852 object->attach(child, object->lastChild());
853 sp_object_unref(child, NULL);
854 child->invoke_build(document, rchild, SP_OBJECT_IS_CLONED(object));
855 }
856 }
858 void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
859 {
860 debug("id=%x, typename=%s", this, g_type_name_from_instance((GTypeInstance*)this));
862 //g_assert(object != NULL);
863 //g_assert(SP_IS_OBJECT(object));
864 g_assert(document != NULL);
865 g_assert(repr != NULL);
867 g_assert(this->document == NULL);
868 g_assert(this->repr == NULL);
869 g_assert(this->getId() == NULL);
871 /* Bookkeeping */
873 this->document = document;
874 this->repr = repr;
875 if (!cloned)
876 Inkscape::GC::anchor(repr);
877 this->cloned = cloned;
879 if (!SP_OBJECT_IS_CLONED(this)) {
880 this->document->bindObjectToRepr(this->repr, this);
882 if (Inkscape::XML::id_permitted(this->repr)) {
883 /* If we are not cloned, and not seeking, force unique id */
884 gchar const *id = this->repr->attribute("id");
885 if (!document->isSeeking()) {
886 {
887 gchar *realid = sp_object_get_unique_id(this, id);
888 g_assert(realid != NULL);
890 this->document->bindObjectToId(realid, this);
891 SPObjectImpl::setId(this, realid);
892 g_free(realid);
893 }
895 /* Redefine ID, if required */
896 if ((id == NULL) || (strcmp(id, this->getId()) != 0)) {
897 this->repr->setAttribute("id", this->getId());
898 }
899 } else if (id) {
900 // bind if id, but no conflict -- otherwise, we can expect
901 // a subsequent setting of the id attribute
902 if (!this->document->getObjectById(id)) {
903 this->document->bindObjectToId(id, this);
904 SPObjectImpl::setId(this, id);
905 }
906 }
907 }
908 } else {
909 g_assert(this->getId() == NULL);
910 }
912 /* Invoke derived methods, if any */
913 if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->build) {
914 (*((SPObjectClass *) G_OBJECT_GET_CLASS(this))->build)(this, document, repr);
915 }
917 /* Signalling (should be connected AFTER processing derived methods */
918 sp_repr_add_listener(repr, &object_event_vector, this);
919 }
921 void SPObject::releaseReferences() {
922 g_assert(this->document);
923 g_assert(this->repr);
925 sp_repr_remove_listener_by_data(this->repr, this);
927 this->_release_signal.emit(this);
928 SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
929 if (klass->release) {
930 klass->release(this);
931 }
933 /* all hrefs should be released by the "release" handlers */
934 g_assert(this->hrefcount == 0);
936 if (!SP_OBJECT_IS_CLONED(this)) {
937 if (this->id) {
938 this->document->bindObjectToId(this->id, NULL);
939 }
940 g_free(this->id);
941 this->id = NULL;
943 g_free(this->_default_label);
944 this->_default_label = NULL;
946 this->document->bindObjectToRepr(this->repr, NULL);
948 Inkscape::GC::release(this->repr);
949 } else {
950 g_assert(!this->id);
951 }
953 if (this->style) {
954 this->style = sp_style_unref(this->style);
955 }
957 this->document = NULL;
958 this->repr = NULL;
959 }
962 SPObject *SPObject::getNext()
963 {
964 return next;
965 }
967 SPObject *SPObject::getPrev()
968 {
969 return sp_object_prev(this);
970 }
972 /**
973 * Callback for child_added node event.
974 */
975 static void
976 sp_object_repr_child_added(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data)
977 {
978 SPObject *object = SP_OBJECT(data);
980 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->child_added)
981 (*((SPObjectClass *)G_OBJECT_GET_CLASS(object))->child_added)(object, child, ref);
982 }
984 /**
985 * Callback for remove_child node event.
986 */
987 static void
988 sp_object_repr_child_removed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, gpointer data)
989 {
990 SPObject *object = SP_OBJECT(data);
992 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->remove_child) {
993 (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->remove_child)(object, child);
994 }
995 }
997 /**
998 * Callback for order_changed node event.
999 *
1000 * \todo fixme:
1001 */
1002 static void
1003 sp_object_repr_order_changed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data)
1004 {
1005 SPObject *object = SP_OBJECT(data);
1007 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->order_changed) {
1008 (* ((SPObjectClass *)G_OBJECT_GET_CLASS(object))->order_changed)(object, child, old, newer);
1009 }
1010 }
1012 /**
1013 * Callback for set event.
1014 */
1015 static void
1016 sp_object_private_set(SPObject *object, unsigned int key, gchar const *value)
1017 {
1018 g_assert(key != SP_ATTR_INVALID);
1020 switch (key) {
1021 case SP_ATTR_ID:
1023 //XML Tree being used here.
1024 if ( !SP_OBJECT_IS_CLONED(object) && object->getRepr()->type() == Inkscape::XML::ELEMENT_NODE ) {
1025 SPDocument *document=object->document;
1026 SPObject *conflict=NULL;
1028 gchar const *new_id = value;
1030 if (new_id) {
1031 conflict = document->getObjectById((char const *)new_id);
1032 }
1034 if ( conflict && conflict != object ) {
1035 if (!document->isSeeking()) {
1036 sp_object_ref(conflict, NULL);
1037 // give the conflicting object a new ID
1038 gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL);
1039 SP_OBJECT_REPR(conflict)->setAttribute("id", new_conflict_id);
1040 g_free(new_conflict_id);
1041 sp_object_unref(conflict, NULL);
1042 } else {
1043 new_id = NULL;
1044 }
1045 }
1047 if (object->getId()) {
1048 document->bindObjectToId(object->getId(), NULL);
1049 SPObjectImpl::setId(object, 0);
1050 }
1052 if (new_id) {
1053 SPObjectImpl::setId(object, new_id);
1054 document->bindObjectToId(object->getId(), object);
1055 }
1057 g_free(object->_default_label);
1058 object->_default_label = NULL;
1059 }
1060 break;
1061 case SP_ATTR_INKSCAPE_LABEL:
1062 g_free(object->_label);
1063 if (value) {
1064 object->_label = g_strdup(value);
1065 } else {
1066 object->_label = NULL;
1067 }
1068 g_free(object->_default_label);
1069 object->_default_label = NULL;
1070 break;
1071 case SP_ATTR_INKSCAPE_COLLECT:
1072 if ( value && !strcmp(value, "always") ) {
1073 object->setCollectionPolicy(SPObject::ALWAYS_COLLECT);
1074 } else {
1075 object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT);
1076 }
1077 break;
1078 case SP_ATTR_XML_SPACE:
1079 if (value && !strcmp(value, "preserve")) {
1080 object->xml_space.value = SP_XML_SPACE_PRESERVE;
1081 object->xml_space.set = TRUE;
1082 } else if (value && !strcmp(value, "default")) {
1083 object->xml_space.value = SP_XML_SPACE_DEFAULT;
1084 object->xml_space.set = TRUE;
1085 } else if (object->parent) {
1086 SPObject *parent;
1087 parent = object->parent;
1088 object->xml_space.value = parent->xml_space.value;
1089 }
1090 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1091 break;
1092 case SP_ATTR_STYLE:
1093 sp_style_read_from_object(object->style, object);
1094 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1095 break;
1096 default:
1097 break;
1098 }
1099 }
1101 /**
1102 * Call virtual set() function of object.
1103 */
1104 void
1105 sp_object_set(SPObject *object, unsigned int key, gchar const *value)
1106 {
1107 g_assert(object != NULL);
1108 g_assert(SP_IS_OBJECT(object));
1110 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set) {
1111 ((SPObjectClass *) G_OBJECT_GET_CLASS(object))->set(object, key, value);
1112 }
1113 }
1115 /**
1116 * Read value of key attribute from XML node into object.
1117 */
1118 void
1119 sp_object_read_attr(SPObject *object, gchar const *key)
1120 {
1121 g_assert(object != NULL);
1122 g_assert(SP_IS_OBJECT(object));
1123 g_assert(key != NULL);
1125 //XML Tree being used here.
1126 g_assert(object->getRepr() != NULL);
1128 unsigned int keyid = sp_attribute_lookup(key);
1129 if (keyid != SP_ATTR_INVALID) {
1130 /* Retrieve the 'key' attribute from the object's XML representation */
1131 gchar const *value = object->getRepr()->attribute(key);
1133 sp_object_set(object, keyid, value);
1134 }
1135 }
1137 /**
1138 * Callback for attr_changed node event.
1139 */
1140 static void
1141 sp_object_repr_attr_changed(Inkscape::XML::Node */*repr*/, gchar const *key, gchar const */*oldval*/, gchar const */*newval*/, bool is_interactive, gpointer data)
1142 {
1143 SPObject *object = SP_OBJECT(data);
1145 sp_object_read_attr(object, key);
1147 // manual changes to extension attributes require the normal
1148 // attributes, which depend on them, to be updated immediately
1149 if (is_interactive) {
1150 object->updateRepr(0);
1151 }
1152 }
1154 /**
1155 * Callback for content_changed node event.
1156 */
1157 static void
1158 sp_object_repr_content_changed(Inkscape::XML::Node */*repr*/, gchar const */*oldcontent*/, gchar const */*newcontent*/, gpointer data)
1159 {
1160 SPObject *object = SP_OBJECT(data);
1162 if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)
1163 (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->read_content)(object);
1164 }
1166 /**
1167 * Return string representation of space value.
1168 */
1169 static gchar const*
1170 sp_xml_get_space_string(unsigned int space)
1171 {
1172 switch (space) {
1173 case SP_XML_SPACE_DEFAULT:
1174 return "default";
1175 case SP_XML_SPACE_PRESERVE:
1176 return "preserve";
1177 default:
1178 return NULL;
1179 }
1180 }
1182 /**
1183 * Callback for write event.
1184 */
1185 static Inkscape::XML::Node *
1186 sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags)
1187 {
1188 if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) {
1189 repr = SP_OBJECT_REPR(object)->duplicate(doc);
1190 if (!( flags & SP_OBJECT_WRITE_EXT )) {
1191 repr->setAttribute("inkscape:collect", NULL);
1192 }
1193 } else {
1194 repr->setAttribute("id", object->getId());
1196 if (object->xml_space.set) {
1197 char const *xml_space;
1198 xml_space = sp_xml_get_space_string(object->xml_space.value);
1199 repr->setAttribute("xml:space", xml_space);
1200 }
1202 if ( flags & SP_OBJECT_WRITE_EXT &&
1203 object->collectionPolicy() == SPObject::ALWAYS_COLLECT )
1204 {
1205 repr->setAttribute("inkscape:collect", "always");
1206 } else {
1207 repr->setAttribute("inkscape:collect", NULL);
1208 }
1210 SPStyle const *const obj_style = SP_OBJECT_STYLE(object);
1211 if (obj_style) {
1212 gchar *s = sp_style_write_string(obj_style, SP_STYLE_FLAG_IFSET);
1213 repr->setAttribute("style", ( *s ? s : NULL ));
1214 g_free(s);
1215 } else {
1216 /** \todo I'm not sure what to do in this case. Bug #1165868
1217 * suggests that it can arise, but the submitter doesn't know
1218 * how to do so reliably. The main two options are either
1219 * leave repr's style attribute unchanged, or explicitly clear it.
1220 * Must also consider what to do with property attributes for
1221 * the element; see below.
1222 */
1223 char const *style_str = repr->attribute("style");
1224 if (!style_str) {
1225 style_str = "NULL";
1226 }
1227 g_warning("Item's style is NULL; repr style attribute is %s", style_str);
1228 }
1230 /** \note We treat object->style as authoritative. Its effects have
1231 * been written to the style attribute above; any properties that are
1232 * unset we take to be deliberately unset (e.g. so that clones can
1233 * override the property).
1234 *
1235 * Note that the below has an undesirable consequence of changing the
1236 * appearance on renderers that lack CSS support (e.g. SVG tiny);
1237 * possibly we should write property attributes instead of a style
1238 * attribute.
1239 */
1240 sp_style_unset_property_attrs (object);
1241 }
1243 return repr;
1244 }
1246 /**
1247 * Update this object's XML node with flags value.
1248 */
1249 Inkscape::XML::Node *
1250 SPObject::updateRepr(unsigned int flags) {
1251 if (!SP_OBJECT_IS_CLONED(this)) {
1252 Inkscape::XML::Node *repr=SP_OBJECT_REPR(this);
1253 if (repr) {
1254 return updateRepr(repr->document(), repr, flags);
1255 } else {
1256 g_critical("Attempt to update non-existent repr");
1257 return NULL;
1258 }
1259 } else {
1260 /* cloned objects have no repr */
1261 return NULL;
1262 }
1263 }
1265 /** Used both to create reprs in the original document, and to create
1266 * reprs in another document (e.g. a temporary document used when
1267 * saving as "Plain SVG"
1268 */
1269 Inkscape::XML::Node *
1270 SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) {
1271 g_assert(doc != NULL);
1273 if (SP_OBJECT_IS_CLONED(this)) {
1274 /* cloned objects have no repr */
1275 return NULL;
1276 }
1277 if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write) {
1278 if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1279 repr = SP_OBJECT_REPR(this);
1280 }
1281 return ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write(this, doc, repr, flags);
1282 } else {
1283 g_warning("Class %s does not implement ::write", G_OBJECT_TYPE_NAME(this));
1284 if (!repr) {
1285 if (flags & SP_OBJECT_WRITE_BUILD) {
1286 repr = SP_OBJECT_REPR(this)->duplicate(doc);
1287 }
1288 /// \todo FIXME: else probably error (Lauris) */
1289 } else {
1290 repr->mergeFrom(SP_OBJECT_REPR(this), "id");
1291 }
1292 return repr;
1293 }
1294 }
1296 /* Modification */
1298 /**
1299 * Add \a flags to \a object's as dirtiness flags, and
1300 * recursively add CHILD_MODIFIED flag to
1301 * parent and ancestors (as far up as necessary).
1302 */
1303 void
1304 SPObject::requestDisplayUpdate(unsigned int flags)
1305 {
1306 g_return_if_fail( this->document != NULL );
1308 if (update_in_progress) {
1309 g_print("WARNING: Requested update while update in progress, counter = %d\n", update_in_progress);
1310 }
1312 /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1313 * SP_OBJECT_CHILD_MODIFIED_FLAG */
1314 g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1315 g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1316 g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1318 bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1320 this->uflags |= flags;
1322 /* If requestModified has already been called on this object or one of its children, then we
1323 * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1324 */
1325 if (already_propagated) {
1326 SPObject *parent = SP_OBJECT_PARENT(this);
1327 if (parent) {
1328 parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG);
1329 } else {
1330 SP_OBJECT_DOCUMENT(this)->request_modified();
1331 }
1332 }
1333 }
1335 /**
1336 * Update views
1337 */
1338 void
1339 SPObject::updateDisplay(SPCtx *ctx, unsigned int flags)
1340 {
1341 g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1343 update_in_progress ++;
1345 #ifdef SP_OBJECT_DEBUG_CASCADE
1346 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);
1347 #endif
1349 /* Get this flags */
1350 flags |= this->uflags;
1351 /* Copy flags to modified cascade for later processing */
1352 this->mflags |= this->uflags;
1353 /* We have to clear flags here to allow rescheduling update */
1354 this->uflags = 0;
1356 // Merge style if we have good reasons to think that parent style is changed */
1357 /** \todo
1358 * I am not sure whether we should check only propagated
1359 * flag. We are currently assuming that style parsing is
1360 * done immediately. I think this is correct (Lauris).
1361 */
1362 if ((flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) {
1363 if (this->style && this->parent) {
1364 sp_style_merge_from_parent(this->style, this->parent->style);
1365 }
1366 }
1368 try
1369 {
1370 if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update)
1371 ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);
1372 }
1373 catch(...)
1374 {
1375 /** \todo
1376 * in case of catching an exception we need to inform the user somehow that the document is corrupted
1377 * maybe by implementing an document flag documentOk
1378 * or by a modal error dialog
1379 */
1380 g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);");
1381 }
1383 update_in_progress --;
1384 }
1386 /**
1387 * Request modified always bubbles *up* the tree, as opposed to
1388 * request display update, which trickles down and relies on the
1389 * flags set during this pass...
1390 */
1391 void
1392 SPObject::requestModified(unsigned int flags)
1393 {
1394 g_return_if_fail( this->document != NULL );
1396 /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
1397 * SP_OBJECT_CHILD_MODIFIED_FLAG */
1398 g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG));
1399 g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG));
1400 g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)));
1402 bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)));
1404 this->mflags |= flags;
1406 /* If requestModified has already been called on this object or one of its children, then we
1407 * don't need to set CHILD_MODIFIED on our ancestors because it's already been done.
1408 */
1409 if (already_propagated) {
1410 SPObject *parent=SP_OBJECT_PARENT(this);
1411 if (parent) {
1412 parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG);
1413 } else {
1414 SP_OBJECT_DOCUMENT(this)->request_modified();
1415 }
1416 }
1417 }
1419 /**
1420 * Emits the MODIFIED signal with the object's flags.
1421 * The object's mflags are the original set aside during the update pass for
1422 * later delivery here. Once emitModified() is called, those flags don't
1423 * need to be stored any longer.
1424 */
1425 void
1426 SPObject::emitModified(unsigned int flags)
1427 {
1428 /* only the MODIFIED_CASCADE flag is legal here */
1429 g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE));
1431 #ifdef SP_OBJECT_DEBUG_CASCADE
1432 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);
1433 #endif
1435 flags |= this->mflags;
1436 /* We have to clear mflags beforehand, as signal handlers may
1437 * make changes and therefore queue new modification notifications
1438 * themselves. */
1439 this->mflags = 0;
1441 g_object_ref(G_OBJECT(this));
1442 SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this);
1443 if (klass->modified) {
1444 klass->modified(this, flags);
1445 }
1446 _modified_signal.emit(this, flags);
1447 g_object_unref(G_OBJECT(this));
1448 }
1450 gchar const *
1451 sp_object_tagName_get(SPObject const *object, SPException *ex)
1452 {
1453 /* If exception is not clear, return */
1454 if (!SP_EXCEPTION_IS_OK(ex)) {
1455 return NULL;
1456 }
1458 /// \todo fixme: Exception if object is NULL? */
1459 //XML Tree being used here.
1460 return object->getRepr()->name();
1461 }
1463 gchar const *
1464 sp_object_getAttribute(SPObject const *object, gchar const *key, SPException *ex)
1465 {
1466 /* If exception is not clear, return */
1467 if (!SP_EXCEPTION_IS_OK(ex)) {
1468 return NULL;
1469 }
1471 /// \todo fixme: Exception if object is NULL? */
1472 //XML Tree being used here.
1473 return (gchar const *) object->getRepr()->attribute(key);
1474 }
1476 void
1477 sp_object_setAttribute(SPObject *object, gchar const *key, gchar const *value, SPException *ex)
1478 {
1479 /* If exception is not clear, return */
1480 g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1482 /// \todo fixme: Exception if object is NULL? */
1483 //XML Tree being used here.
1484 object->getRepr()->setAttribute(key, value, false);
1485 }
1487 void
1488 sp_object_removeAttribute(SPObject *object, gchar const *key, SPException *ex)
1489 {
1490 /* If exception is not clear, return */
1491 g_return_if_fail(SP_EXCEPTION_IS_OK(ex));
1493 /// \todo fixme: Exception if object is NULL? */
1494 //XML Tree being used here.
1495 object->getRepr()->setAttribute(key, NULL, false);
1496 }
1498 /* Helper */
1500 static gchar *
1501 sp_object_get_unique_id(SPObject *object, gchar const *id)
1502 {
1503 static unsigned long count = 0;
1505 g_assert(SP_IS_OBJECT(object));
1507 count++;
1509 //XML Tree being used here.
1510 gchar const *name = object->getRepr()->name();
1511 g_assert(name != NULL);
1513 gchar const *local = strchr(name, ':');
1514 if (local) {
1515 name = local + 1;
1516 }
1518 if (id != NULL) {
1519 if (object->document->getObjectById(id) == NULL) {
1520 return g_strdup(id);
1521 }
1522 }
1524 size_t const name_len = strlen(name);
1525 size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1;
1526 gchar *const buf = (gchar *) g_malloc(buflen);
1527 memcpy(buf, name, name_len);
1528 gchar *const count_buf = buf + name_len;
1529 size_t const count_buflen = buflen - name_len;
1530 do {
1531 ++count;
1532 g_snprintf(count_buf, count_buflen, "%lu", count);
1533 } while ( object->document->getObjectById(buf) != NULL );
1534 return buf;
1535 }
1537 /* Style */
1539 /**
1540 * Returns an object style property.
1541 *
1542 * \todo
1543 * fixme: Use proper CSS parsing. The current version is buggy
1544 * in a number of situations where key is a substring of the
1545 * style string other than as a property name (including
1546 * where key is a substring of a property name), and is also
1547 * buggy in its handling of inheritance for properties that
1548 * aren't inherited by default. It also doesn't allow for
1549 * the case where the property is specified but with an invalid
1550 * value (in which case I believe the CSS2 error-handling
1551 * behaviour applies, viz. behave as if the property hadn't
1552 * been specified). Also, the current code doesn't use CRSelEng
1553 * stuff to take a value from stylesheets. Also, we aren't
1554 * setting any hooks to force an update for changes in any of
1555 * the inputs (i.e., in any of the elements that this function
1556 * queries).
1557 *
1558 * \par
1559 * Given that the default value for a property depends on what
1560 * property it is (e.g., whether to inherit or not), and given
1561 * the above comment about ignoring invalid values, and that the
1562 * repr parent isn't necessarily the right element to inherit
1563 * from (e.g., maybe we need to inherit from the referencing
1564 * <use> element instead), we should probably make the caller
1565 * responsible for ascending the repr tree as necessary.
1566 */
1567 gchar const *
1568 sp_object_get_style_property(SPObject const *object, gchar const *key, gchar const *def)
1569 {
1570 g_return_val_if_fail(object != NULL, NULL);
1571 g_return_val_if_fail(SP_IS_OBJECT(object), NULL);
1572 g_return_val_if_fail(key != NULL, NULL);
1574 //XML Tree being used here.
1575 gchar const *style = object->getRepr()->attribute("style");
1576 if (style) {
1577 size_t const len = strlen(key);
1578 char const *p;
1579 while ( (p = strstr(style, key))
1580 != NULL )
1581 {
1582 p += len;
1583 while ((*p <= ' ') && *p) p++;
1584 if (*p++ != ':') break;
1585 while ((*p <= ' ') && *p) p++;
1586 size_t const inherit_len = sizeof("inherit") - 1;
1587 if (*p
1588 && !(strneq(p, "inherit", inherit_len)
1589 && (p[inherit_len] == '\0'
1590 || p[inherit_len] == ';'
1591 || g_ascii_isspace(p[inherit_len])))) {
1592 return p;
1593 }
1594 }
1595 }
1597 //XML Tree being used here.
1598 gchar const *val = object->getRepr()->attribute(key);
1599 if (val && !streq(val, "inherit")) {
1600 return val;
1601 }
1602 if (object->parent) {
1603 return sp_object_get_style_property(object->parent, key, def);
1604 }
1606 return def;
1607 }
1609 /**
1610 * Lifts SVG version of all root objects to version.
1611 */
1612 void
1613 SPObject::_requireSVGVersion(Inkscape::Version version) {
1614 for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) {
1615 SPObject *object=iter;
1616 if (SP_IS_ROOT(object)) {
1617 SPRoot *root=SP_ROOT(object);
1618 if ( root->version.svg < version ) {
1619 root->version.svg = version;
1620 }
1621 }
1622 }
1623 }
1625 /**
1626 * Returns previous object in sibling list or NULL.
1627 */
1628 SPObject *
1629 sp_object_prev(SPObject *child)
1630 {
1631 SPObject *parent = SP_OBJECT_PARENT(child);
1632 for ( SPObject *i = parent->first_child(); i; i = SP_OBJECT_NEXT(i) ) {
1633 if (SP_OBJECT_NEXT(i) == child)
1634 return i;
1635 }
1636 return NULL;
1637 }
1639 /* Titles and descriptions */
1641 /* Note:
1642 Titles and descriptions are stored in 'title' and 'desc' child elements
1643 (see section 5.4 of the SVG 1.0 and 1.1 specifications). The spec allows
1644 an element to have more than one 'title' child element, but strongly
1645 recommends against this and requires using the first one if a choice must
1646 be made. The same applies to 'desc' elements. Therefore, these functions
1647 ignore all but the first 'title' child element and first 'desc' child
1648 element, except when deleting a title or description.
1649 */
1651 /**
1652 * Returns the title of this object, or NULL if there is none.
1653 * The caller must free the returned string using g_free() - see comment
1654 * for getTitleOrDesc() below.
1655 */
1656 gchar *
1657 SPObject::title() const
1658 {
1659 return getTitleOrDesc("svg:title");
1660 }
1662 /**
1663 * Sets the title of this object
1664 * A NULL first argument is interpreted as meaning that the existing title
1665 * (if any) should be deleted.
1666 * The second argument is optional - see setTitleOrDesc() below for details.
1667 */
1668 bool
1669 SPObject::setTitle(gchar const *title, bool verbatim)
1670 {
1671 return setTitleOrDesc(title, "svg:title", verbatim);
1672 }
1674 /**
1675 * Returns the description of this object, or NULL if there is none.
1676 * The caller must free the returned string using g_free() - see comment
1677 * for getTitleOrDesc() below.
1678 */
1679 gchar *
1680 SPObject::desc() const
1681 {
1682 return getTitleOrDesc("svg:desc");
1683 }
1685 /**
1686 * Sets the description of this object.
1687 * A NULL first argument is interpreted as meaning that the existing
1688 * description (if any) should be deleted.
1689 * The second argument is optional - see setTitleOrDesc() below for details.
1690 */
1691 bool
1692 SPObject::setDesc(gchar const *desc, bool verbatim)
1693 {
1694 return setTitleOrDesc(desc, "svg:desc", verbatim);
1695 }
1697 /**
1698 * Returns the title or description of this object, or NULL if there is none.
1699 *
1700 * The SVG spec allows 'title' and 'desc' elements to contain text marked up
1701 * using elements from other namespaces. Therefore, this function cannot
1702 * in general just return a pointer to an existing string - it must instead
1703 * construct a string containing the title or description without the mark-up.
1704 * Consequently, the return value is a newly allocated string (or NULL), and
1705 * must be freed (using g_free()) by the caller.
1706 */
1707 gchar *
1708 SPObject::getTitleOrDesc(gchar const *svg_tagname) const
1709 {
1710 SPObject *elem = findFirstChild(svg_tagname);
1711 if (elem == NULL) return NULL;
1712 return g_string_free(elem->textualContent(), FALSE);
1713 }
1715 /**
1716 * Sets or deletes the title or description of this object.
1717 * A NULL 'value' argument causes the title or description to be deleted.
1718 *
1719 * 'verbatim' parameter:
1720 * If verbatim==true, then the title or description is set to exactly the
1721 * specified value. If verbatim==false then two exceptions are made:
1722 * (1) If the specified value is just whitespace, then the title/description
1723 * is deleted.
1724 * (2) If the specified value is the same as the current value except for
1725 * mark-up, then the current value is left unchanged.
1726 * This is usually the desired behaviour, so 'verbatim' defaults to false for
1727 * setTitle() and setDesc().
1728 *
1729 * The return value is true if a change was made to the title/description,
1730 * and usually false otherwise.
1731 */
1732 bool
1733 SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool verbatim)
1734 {
1735 if (!verbatim) {
1736 // If the new title/description is just whitespace,
1737 // treat it as though it were NULL.
1738 if (value) {
1739 bool just_whitespace = true;
1740 for (const gchar *cp = value; *cp; ++cp) {
1741 if (!std::strchr("\r\n \t", *cp)) {
1742 just_whitespace = false;
1743 break;
1744 }
1745 }
1746 if (just_whitespace) value = NULL;
1747 }
1748 // Don't stomp on mark-up if there is no real change.
1749 if (value) {
1750 gchar *current_value = getTitleOrDesc(svg_tagname);
1751 if (current_value) {
1752 bool different = std::strcmp(current_value, value);
1753 g_free(current_value);
1754 if (!different) return false;
1755 }
1756 }
1757 }
1759 SPObject *elem = findFirstChild(svg_tagname);
1761 if (value == NULL) {
1762 if (elem == NULL) return false;
1763 // delete the title/description(s)
1764 while (elem) {
1765 elem->deleteObject();
1766 elem = findFirstChild(svg_tagname);
1767 }
1768 return true;
1769 }
1771 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document);
1773 if (elem == NULL) {
1774 // create a new 'title' or 'desc' element, putting it at the
1775 // beginning (in accordance with the spec's recommendations)
1776 Inkscape::XML::Node *xml_elem = xml_doc->createElement(svg_tagname);
1777 repr->addChild(xml_elem, NULL);
1778 elem = document->getObjectByRepr(xml_elem);
1779 Inkscape::GC::release(xml_elem);
1780 }
1781 else {
1782 // remove the current content of the 'text' or 'desc' element
1783 SPObject *child;
1784 while (NULL != (child = elem->firstChild())) child->deleteObject();
1785 }
1787 // add the new content
1788 elem->appendChildRepr(xml_doc->createTextNode(value));
1789 return true;
1790 }
1792 /**
1793 * Find the first child of this object with a given tag name,
1794 * and return it. Returns NULL if there is no matching child.
1795 */
1796 SPObject *
1797 SPObject::findFirstChild(gchar const *tagname) const
1798 {
1799 for (SPObject *child = children; child; child = child->next)
1800 {
1801 if (child->repr->type() == Inkscape::XML::ELEMENT_NODE &&
1802 !strcmp(child->repr->name(), tagname)) return child;
1803 }
1804 return NULL;
1805 }
1807 /**
1808 * Return the full textual content of an element (typically all the
1809 * content except the tags).
1810 * Must not be used on anything except elements.
1811 */
1812 GString*
1813 SPObject::textualContent() const
1814 {
1815 GString* text = g_string_new("");
1817 for (const SPObject *child = firstChild(); child; child = child->next)
1818 {
1819 Inkscape::XML::NodeType child_type = child->repr->type();
1821 if (child_type == Inkscape::XML::ELEMENT_NODE) {
1822 GString * new_text = child->textualContent();
1823 g_string_append(text, new_text->str);
1824 g_string_free(new_text, TRUE);
1825 }
1826 else if (child_type == Inkscape::XML::TEXT_NODE) {
1827 g_string_append(text, child->repr->content());
1828 }
1829 }
1830 return text;
1831 }
1833 /*
1834 Local Variables:
1835 mode:c++
1836 c-file-style:"stroustrup"
1837 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1838 indent-tabs-mode:nil
1839 fill-column:99
1840 End:
1841 */
1842 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :