X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fsp-object.cpp;h=9037d45a976800f0685033e29d1343473c444a87;hb=76750e3eb87017e5b9aefb0c0148a67bbb3d7232;hp=5f6bf779fabc2d90c011ce32469ee5ddf736a517;hpb=ec988e4e64be7b5c5de708799bb93e818a15f8d4;p=inkscape.git diff --git a/src/sp-object.cpp b/src/sp-object.cpp index 5f6bf779f..9037d45a9 100644 --- a/src/sp-object.cpp +++ b/src/sp-object.cpp @@ -5,8 +5,9 @@ * Authors: * Lauris Kaplinski * bulia byak + * Stephen Silver * - * Copyright (C) 1999-2005 authors + * Copyright (C) 1999-2008 authors * Copyright (C) 2001-2002 Ximian, Inc. * * Released under GNU GPL, read the file 'COPYING' for more information @@ -31,6 +32,8 @@ * dictionary and so on. Source: doc/architecture.txt */ +#include +#include #include "helper/sp-marshal.h" #include "xml/node-event-vector.h" @@ -39,11 +42,17 @@ #include "style.h" #include "sp-object-repr.h" #include "sp-root.h" +#include "sp-style-elem.h" +#include "sp-script.h" #include "streq.h" #include "strneq.h" #include "xml/repr.h" #include "xml/node-fns.h" #include "debug/event-tracker.h" +#include "debug/simple-event.h" +#include "debug/demangle.h" +#include "util/share.h" +#include "util/format.h" #include "algorithms/longest-common-suffix.h" using std::memcpy; @@ -78,7 +87,7 @@ static void sp_object_release(SPObject *object); static void sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); static void sp_object_private_set(SPObject *object, unsigned int key, gchar const *value); -static Inkscape::XML::Node *sp_object_private_write(SPObject *object, Inkscape::XML::Node *repr, guint flags); +static Inkscape::XML::Node *sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags); /* Real handlers of repr signals */ @@ -95,8 +104,6 @@ static gchar *sp_object_get_unique_id(SPObject *object, gchar const *defid); guint update_in_progress = 0; // guard against update-during-update -enum {RELEASE, MODIFIED, LAST_SIGNAL}; - Inkscape::XML::NodeEventVector object_event_vector = { sp_object_repr_child_added, sp_object_repr_child_removed, @@ -106,7 +113,6 @@ Inkscape::XML::NodeEventVector object_event_vector = { }; static GObjectClass *parent_class; -static guint object_signals[LAST_SIGNAL] = {0}; /** * Registers the SPObject class with Gdk and returns its type number. @@ -143,21 +149,6 @@ sp_object_class_init(SPObjectClass *klass) parent_class = (GObjectClass *) g_type_class_ref(G_TYPE_OBJECT); - object_signals[RELEASE] = g_signal_new("release", - G_TYPE_FROM_CLASS(klass), - (GSignalFlags)(G_SIGNAL_RUN_CLEANUP | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS), - G_STRUCT_OFFSET(SPObjectClass, release), - NULL, NULL, - sp_marshal_VOID__VOID, - G_TYPE_NONE, 0); - object_signals[MODIFIED] = g_signal_new("modified", - G_TYPE_FROM_CLASS(klass), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET(SPObjectClass, modified), - NULL, NULL, - sp_marshal_NONE__UINT, - G_TYPE_NONE, 1, G_TYPE_UINT); - object_class->finalize = sp_object_finalize; klass->child_added = sp_object_child_added; @@ -187,14 +178,21 @@ sp_object_init(SPObject *object) object->parent = object->next = NULL; object->repr = NULL; object->id = NULL; - object->style = NULL; object->_collection_policy = SPObject::COLLECT_WITH_PARENT; + new (&object->_release_signal) sigc::signal(); + new (&object->_modified_signal) sigc::signal(); new (&object->_delete_signal) sigc::signal(); new (&object->_position_changed_signal) sigc::signal(); object->_successor = NULL; + // FIXME: now we create style for all objects, but per SVG, only the following can have style attribute: + // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline, + // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient, + // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject + object->style = sp_style_new_from_object(object); + object->_label = NULL; object->_default_label = NULL; } @@ -221,60 +219,42 @@ sp_object_finalize(GObject *object) (* ((GObjectClass *) (parent_class))->finalize)(object); } + spobject->_release_signal.~signal(); + spobject->_modified_signal.~signal(); spobject->_delete_signal.~signal(); spobject->_position_changed_signal.~signal(); } namespace { -Inkscape::Util::shared_ptr stringify(SPObject *obj) { - char *temp=g_strdup_printf("%p", obj); - Inkscape::Util::shared_ptr result=Inkscape::Util::share_string(temp); - g_free(temp); - return result; -} +namespace Debug = Inkscape::Debug; +namespace Util = Inkscape::Util; -Inkscape::Util::shared_ptr stringify(unsigned n) { - char *temp=g_strdup_printf("%u", n); - Inkscape::Util::shared_ptr result=Inkscape::Util::share_string(temp); - g_free(temp); - return result; -} +typedef Debug::SimpleEvent BaseRefCountEvent; -class RefEvent : public Inkscape::Debug::Event { +class RefCountEvent : public BaseRefCountEvent { public: - enum Type { REF, UNREF }; + RefCountEvent(SPObject *object, int bias, Util::ptr_shared name) + : BaseRefCountEvent(name) + { + _addProperty("object", Util::format("%p", object)); + _addProperty("class", Debug::demangle(g_type_name(G_TYPE_FROM_INSTANCE(object)))); + _addProperty("new-refcount", Util::format("%d", G_OBJECT(object)->ref_count + bias)); + } +}; - RefEvent(SPObject *object, Type type) - : _object(stringify(object)), _refcount(G_OBJECT(object)->ref_count), - _type(type) +class RefEvent : public RefCountEvent { +public: + RefEvent(SPObject *object) + : RefCountEvent(object, 1, Util::share_static_string("sp-object-ref")) {} +}; - static Category category() { return REFCOUNT; } - - Inkscape::Util::shared_ptr name() const { - if ( _type == REF) { - return Inkscape::Util::share_static_string("sp-object-ref"); - } else { - return Inkscape::Util::share_static_string("sp-object-unref"); - } - } - unsigned propertyCount() const { return 2; } - PropertyPair property(unsigned index) const { - switch (index) { - case 0: - return PropertyPair("object", _object); - case 1: - return PropertyPair("refcount", stringify( _type == REF ? _refcount + 1 : _refcount - 1 )); - default: - return PropertyPair(); - } - } - -private: - Inkscape::Util::shared_ptr _object; - unsigned _refcount; - Type _type; +class UnrefEvent : public RefCountEvent { +public: + UnrefEvent(SPObject *object) + : RefCountEvent(object, -1, Util::share_static_string("sp-object-unref")) + {} }; } @@ -293,11 +273,8 @@ sp_object_ref(SPObject *object, SPObject *owner) g_return_val_if_fail(SP_IS_OBJECT(object), NULL); g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL); - Inkscape::Debug::EventTracker<> tracker; - tracker.set(object, RefEvent::REF); - + Inkscape::Debug::EventTracker tracker(object); g_object_ref(G_OBJECT(object)); - return object; } @@ -316,11 +293,8 @@ sp_object_unref(SPObject *object, SPObject *owner) g_return_val_if_fail(SP_IS_OBJECT(object), NULL); g_return_val_if_fail(!owner || SP_IS_OBJECT(owner), NULL); - Inkscape::Debug::EventTracker<> tracker; - tracker.set(object, RefEvent::UNREF); - + Inkscape::Debug::EventTracker tracker(object); g_object_unref(G_OBJECT(object)); - return NULL; } @@ -335,7 +309,7 @@ sp_object_unref(SPObject *object, SPObject *owner) * \pre object points to real object */ SPObject * -sp_object_href(SPObject *object, gpointer owner) +sp_object_href(SPObject *object, gpointer /*owner*/) { g_return_val_if_fail(object != NULL, NULL); g_return_val_if_fail(SP_IS_OBJECT(object), NULL); @@ -356,7 +330,7 @@ sp_object_href(SPObject *object, gpointer owner) * \pre object points to real object and hrefcount>0 */ SPObject * -sp_object_hunref(SPObject *object, gpointer owner) +sp_object_hunref(SPObject *object, gpointer /*owner*/) { g_return_val_if_fail(object != NULL, NULL); g_return_val_if_fail(SP_IS_OBJECT(object), NULL); @@ -480,6 +454,22 @@ SPObject::appendChildRepr(Inkscape::XML::Node *repr) { } } +/** + * Retrieves the children as a GSList object, optionally ref'ing the children + * in the process, if add_ref is specified. + */ +GSList *SPObject::childList(bool add_ref, Action) { + GSList *l = NULL; + for (SPObject *child = sp_object_first_child(this) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { + if (add_ref) + g_object_ref (G_OBJECT (child)); + + l = g_slist_prepend (l, child); + } + return l; + +} + /** Gets the label property for the object or a default if no label * is defined. */ @@ -517,6 +507,13 @@ SPObject::setLabel(gchar const *label) { void SPObject::requestOrphanCollection() { g_return_if_fail(document != NULL); + + // do not remove style or script elements (Bug #276244) + if (SP_IS_STYLE_ELEM(this)) + return; + if (SP_IS_SCRIPT(this)) + return; + document->queueForOrphanCollection(this); /** \todo @@ -659,7 +656,7 @@ sp_object_detach(SPObject *parent, SPObject *object) { g_return_if_fail(SP_IS_OBJECT(object)); g_return_if_fail(object->parent == parent); - sp_object_invoke_release(object); + object->releaseReferences(); SPObject *prev=NULL; for ( SPObject *child = parent->children ; child && child != object ; @@ -757,8 +754,9 @@ sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child) { debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); SPObject *ochild = sp_object_get_child_by_repr(object, child); - g_return_if_fail(ochild != NULL); - sp_object_detach(object, ochild); + g_return_if_fail (ochild != NULL || !strcmp("comment", child->name())); // comments have no objects + if (ochild) + sp_object_detach(object, ochild); } /** @@ -767,7 +765,7 @@ sp_object_remove_child(SPObject *object, Inkscape::XML::Node *child) * Invoked whenever the given mutation event happens in the XML tree. * \param old_ref Ignored */ -static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, +static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node */*old_ref*/, Inkscape::XML::Node *new_ref) { SPObject *ochild = sp_object_get_child_by_repr(object, child); @@ -828,27 +826,34 @@ sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::No object->document = document; object->repr = repr; - Inkscape::GC::anchor(repr); + if (!cloned) + Inkscape::GC::anchor(repr); object->cloned = cloned; if (!SP_OBJECT_IS_CLONED(object)) { object->document->bindObjectToRepr(object->repr, object); if (Inkscape::XML::id_permitted(object->repr)) { - /* If we are not cloned, force unique id */ + /* If we are not cloned, and not seeking, force unique id */ gchar const *id = object->repr->attribute("id"); - gchar *realid = sp_object_get_unique_id(object, id); - g_assert(realid != NULL); - - object->document->bindObjectToId(realid, object); - object->id = realid; - - /* Redefine ID, if required */ - if ((id == NULL) || (strcmp(id, realid) != 0)) { - gboolean undo_sensitive=sp_document_get_undo_sensitive(document); - sp_document_set_undo_sensitive(document, FALSE); - object->repr->setAttribute("id", realid); - sp_document_set_undo_sensitive(document, undo_sensitive); + if (!document->isSeeking()) { + gchar *realid = sp_object_get_unique_id(object, id); + g_assert(realid != NULL); + + object->document->bindObjectToId(realid, object); + object->id = realid; + + /* Redefine ID, if required */ + if ((id == NULL) || (strcmp(id, realid) != 0)) { + object->repr->setAttribute("id", realid); + } + } else if (id) { + // bind if id, but no conflict -- otherwise, we can expect + // a subsequent setting of the id attribute + if (!object->document->getObjectById(id)) { + object->document->bindObjectToId(id, object); + object->id = g_strdup(id); + } } } } else { @@ -856,7 +861,6 @@ sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::No } /* Invoke derived methods, if any */ - if (((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build) { (*((SPObjectClass *) G_OBJECT_GET_CLASS(object))->build)(object, document, repr); } @@ -865,52 +869,51 @@ sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::No sp_repr_add_listener(repr, &object_event_vector, object); } -void -sp_object_invoke_release(SPObject *object) -{ - g_assert(object != NULL); - g_assert(SP_IS_OBJECT(object)); - - g_assert(object->document); - g_assert(object->repr); +void SPObject::releaseReferences() { + g_assert(this->document); + g_assert(this->repr); - sp_repr_remove_listener_by_data(object->repr, object); + sp_repr_remove_listener_by_data(this->repr, this); - g_signal_emit(G_OBJECT(object), object_signals[RELEASE], 0); + this->_release_signal.emit(this); + SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this); + if (klass->release) { + klass->release(this); + } /* all hrefs should be released by the "release" handlers */ - g_assert(object->hrefcount == 0); + g_assert(this->hrefcount == 0); - if (!SP_OBJECT_IS_CLONED(object)) { - if (object->id) { - object->document->bindObjectToId(object->id, NULL); + if (!SP_OBJECT_IS_CLONED(this)) { + if (this->id) { + this->document->bindObjectToId(this->id, NULL); } - g_free(object->id); - object->id = NULL; + g_free(this->id); + this->id = NULL; + + g_free(this->_default_label); + this->_default_label = NULL; - g_free(object->_default_label); - object->_default_label = NULL; + this->document->bindObjectToRepr(this->repr, NULL); - object->document->bindObjectToRepr(object->repr, NULL); + Inkscape::GC::release(this->repr); } else { - g_assert(!object->id); + g_assert(!this->id); } - if (object->style) { - object->style = sp_style_unref(object->style); + if (this->style) { + this->style = sp_style_unref(this->style); } - Inkscape::GC::release(object->repr); - - object->document = NULL; - object->repr = NULL; + this->document = NULL; + this->repr = NULL; } /** * Callback for child_added node event. */ static void -sp_object_repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data) +sp_object_repr_child_added(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data) { SPObject *object = SP_OBJECT(data); @@ -922,7 +925,7 @@ sp_object_repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child * Callback for remove_child node event. */ static void -sp_object_repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data) +sp_object_repr_child_removed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node */*ref*/, gpointer data) { SPObject *object = SP_OBJECT(data); @@ -937,7 +940,7 @@ sp_object_repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *chi * \todo fixme: */ 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) +sp_object_repr_order_changed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data) { SPObject *object = SP_OBJECT(data); @@ -960,16 +963,23 @@ sp_object_private_set(SPObject *object, unsigned int key, gchar const *value) SPDocument *document=object->document; SPObject *conflict=NULL; - if (value) { - conflict = document->getObjectById((char const *)value); + gchar const *new_id = value; + + if (new_id) { + conflict = document->getObjectById((char const *)new_id); } + if ( conflict && conflict != object ) { - sp_object_ref(conflict, NULL); - // give the conflicting object a new ID - gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL); - SP_OBJECT_REPR(conflict)->setAttribute("id", new_conflict_id); - g_free(new_conflict_id); - sp_object_unref(conflict, NULL); + if (!document->isSeeking()) { + sp_object_ref(conflict, NULL); + // give the conflicting object a new ID + gchar *new_conflict_id = sp_object_get_unique_id(conflict, NULL); + SP_OBJECT_REPR(conflict)->setAttribute("id", new_conflict_id); + g_free(new_conflict_id); + sp_object_unref(conflict, NULL); + } else { + new_id = NULL; + } } if (object->id) { @@ -977,8 +987,8 @@ sp_object_private_set(SPObject *object, unsigned int key, gchar const *value) g_free(object->id); } - if (value) { - object->id = g_strdup((char const*)value); + if (new_id) { + object->id = g_strdup((char const*)new_id); document->bindObjectToId(object->id, object); } else { object->id = NULL; @@ -1019,6 +1029,10 @@ sp_object_private_set(SPObject *object, unsigned int key, gchar const *value) } object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); break; + case SP_ATTR_STYLE: + sp_style_read_from_object(object->style, object); + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); + break; default: break; } @@ -1063,7 +1077,7 @@ sp_object_read_attr(SPObject *object, gchar const *key) * Callback for attr_changed node event. */ 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) +sp_object_repr_attr_changed(Inkscape::XML::Node */*repr*/, gchar const *key, gchar const */*oldval*/, gchar const */*newval*/, bool is_interactive, gpointer data) { SPObject *object = SP_OBJECT(data); @@ -1072,7 +1086,7 @@ sp_object_repr_attr_changed(Inkscape::XML::Node *repr, gchar const *key, gchar c // manual changes to extension attributes require the normal // attributes, which depend on them, to be updated immediately if (is_interactive) { - object->updateRepr(repr, 0); + object->updateRepr(0); } } @@ -1080,7 +1094,7 @@ sp_object_repr_attr_changed(Inkscape::XML::Node *repr, gchar const *key, gchar c * Callback for content_changed node event. */ static void -sp_object_repr_content_changed(Inkscape::XML::Node *repr, gchar const *oldcontent, gchar const *newcontent, gpointer data) +sp_object_repr_content_changed(Inkscape::XML::Node */*repr*/, gchar const */*oldcontent*/, gchar const */*newcontent*/, gpointer data) { SPObject *object = SP_OBJECT(data); @@ -1108,10 +1122,10 @@ sp_xml_get_space_string(unsigned int space) * Callback for write event. */ static Inkscape::XML::Node * -sp_object_private_write(SPObject *object, Inkscape::XML::Node *repr, guint flags) +sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) { - repr = SP_OBJECT_REPR(object)->duplicate(); + repr = SP_OBJECT_REPR(object)->duplicate(doc); if (!( flags & SP_OBJECT_WRITE_EXT )) { repr->setAttribute("inkscape:collect", NULL); } @@ -1131,6 +1145,38 @@ sp_object_private_write(SPObject *object, Inkscape::XML::Node *repr, guint flags } else { repr->setAttribute("inkscape:collect", NULL); } + + SPStyle const *const obj_style = SP_OBJECT_STYLE(object); + if (obj_style) { + gchar *s = sp_style_write_string(obj_style, SP_STYLE_FLAG_IFSET); + repr->setAttribute("style", ( *s ? s : NULL )); + g_free(s); + } else { + /** \todo I'm not sure what to do in this case. Bug #1165868 + * suggests that it can arise, but the submitter doesn't know + * how to do so reliably. The main two options are either + * leave repr's style attribute unchanged, or explicitly clear it. + * Must also consider what to do with property attributes for + * the element; see below. + */ + char const *style_str = repr->attribute("style"); + if (!style_str) { + style_str = "NULL"; + } + g_warning("Item's style is NULL; repr style attribute is %s", style_str); + } + + /** \note We treat object->style as authoritative. Its effects have + * been written to the style attribute above; any properties that are + * unset we take to be deliberately unset (e.g. so that clones can + * override the property). + * + * Note that the below has an undesirable consequence of changing the + * appearance on renderers that lack CSS support (e.g. SVG tiny); + * possibly we should write property attributes instead of a style + * attribute. + */ + sp_style_unset_property_attrs (object); } return repr; @@ -1144,7 +1190,7 @@ SPObject::updateRepr(unsigned int flags) { if (!SP_OBJECT_IS_CLONED(this)) { Inkscape::XML::Node *repr=SP_OBJECT_REPR(this); if (repr) { - return updateRepr(repr, flags); + return updateRepr(repr->document(), repr, flags); } else { g_critical("Attempt to update non-existent repr"); return NULL; @@ -1155,8 +1201,14 @@ SPObject::updateRepr(unsigned int flags) { } } +/** Used both to create reprs in the original document, and to create + * reprs in another document (e.g. a temporary document used when + * saving as "Plain SVG" + */ Inkscape::XML::Node * -SPObject::updateRepr(Inkscape::XML::Node *repr, unsigned int flags) { +SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) { + g_assert(doc != NULL); + if (SP_OBJECT_IS_CLONED(this)) { /* cloned objects have no repr */ return NULL; @@ -1165,14 +1217,14 @@ SPObject::updateRepr(Inkscape::XML::Node *repr, unsigned int flags) { if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) { repr = SP_OBJECT_REPR(this); } - return ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write(this, repr, flags); + return ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->write(this, doc, repr, flags); } else { g_warning("Class %s does not implement ::write", G_OBJECT_TYPE_NAME(this)); if (!repr) { if (flags & SP_OBJECT_WRITE_BUILD) { - repr = SP_OBJECT_REPR(this)->duplicate(); + repr = SP_OBJECT_REPR(this)->duplicate(doc); } - /// \todo fixme: else probably error (Lauris) */ + /// \todo FIXME: else probably error (Lauris) */ } else { repr->mergeFrom(SP_OBJECT_REPR(this), "id"); } @@ -1190,30 +1242,38 @@ SPObject::updateRepr(Inkscape::XML::Node *repr, unsigned int flags) { void SPObject::requestDisplayUpdate(unsigned int flags) { + g_return_if_fail( this->document != NULL ); + if (update_in_progress) { g_print("WARNING: Requested update while update in progress, counter = %d\n", update_in_progress); } + /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or + * SP_OBJECT_CHILD_MODIFIED_FLAG */ g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG)); g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)); g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG))); - /* Check for propagate before we set any flags */ - /* Propagate means, that this is not passed through by modification request cascade yet */ - unsigned int propagate = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))); + bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))); - /* Just set this flags safe even if some have been set before */ this->uflags |= flags; - if (propagate) { - if (this->parent) { - this->parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG); + /* If requestModified has already been called on this object or one of its children, then we + * don't need to set CHILD_MODIFIED on our ancestors because it's already been done. + */ + if (already_propagated) { + SPObject *parent = SP_OBJECT_PARENT(this); + if (parent) { + parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG); } else { - sp_document_request_modified(this->document); + sp_document_request_modified(SP_OBJECT_DOCUMENT(this)); } } } +/** + * Update views + */ void SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) { @@ -1244,35 +1304,48 @@ SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) } } - if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update) - ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags); + try + { + if (((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update) + ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags); + } + catch(...) + { + /** \todo + * in case of catching an exception we need to inform the user somehow that the document is corrupted + * maybe by implementing an document flag documentOk + * or by a modal error dialog + */ + g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);"); + } update_in_progress --; } +/** + * Request modified always bubbles *up* the tree, as opposed to + * request display update, which trickles down and relies on the + * flags set during this pass... + */ void SPObject::requestModified(unsigned int flags) { g_return_if_fail( this->document != NULL ); - /* PARENT_MODIFIED is computed later on and is not intended to be - * "manually" queued */ + /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or + * SP_OBJECT_CHILD_MODIFIED_FLAG */ g_return_if_fail(!(flags & SP_OBJECT_PARENT_MODIFIED_FLAG)); - - /* we should be setting either MODIFIED or CHILD_MODIFIED... */ g_return_if_fail((flags & SP_OBJECT_MODIFIED_FLAG) || (flags & SP_OBJECT_CHILD_MODIFIED_FLAG)); - - /* ...but not both */ g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG))); - unsigned int old_mflags=this->mflags; + bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))); + this->mflags |= flags; - /* If we already had MODIFIED or CHILD_MODIFIED queued, we will - * have already queued CHILD_MODIFIED with our ancestors and - * need not disturb them again. + /* If requestModified has already been called on this object or one of its children, then we + * don't need to set CHILD_MODIFIED on our ancestors because it's already been done. */ - if (!( old_mflags & ( SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG ) )) { + if (already_propagated) { SPObject *parent=SP_OBJECT_PARENT(this); if (parent) { parent->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG); @@ -1282,6 +1355,12 @@ SPObject::requestModified(unsigned int flags) } } +/** + * Emits the MODIFIED signal with the object's flags. + * The object's mflags are the original set aside during the update pass for + * later delivery here. Once emitModified() is called, those flags don't + * need to be stored any longer. + */ void SPObject::emitModified(unsigned int flags) { @@ -1299,40 +1378,14 @@ SPObject::emitModified(unsigned int flags) this->mflags = 0; g_object_ref(G_OBJECT(this)); - g_signal_emit(G_OBJECT(this), object_signals[MODIFIED], 0, flags); + SPObjectClass *klass=(SPObjectClass *)G_OBJECT_GET_CLASS(this); + if (klass->modified) { + klass->modified(this, flags); + } + _modified_signal.emit(this, flags); g_object_unref(G_OBJECT(this)); } -/* - * Get and set descriptive parameters - * - * These are inefficent, so they are not intended to be used interactively - */ - -gchar const * -sp_object_title_get(SPObject *object) -{ - return NULL; -} - -gchar const * -sp_object_description_get(SPObject *object) -{ - return NULL; -} - -unsigned int -sp_object_title_set(SPObject *object, gchar const *title) -{ - return FALSE; -} - -unsigned int -sp_object_description_set(SPObject *object, gchar const *desc) -{ - return FALSE; -} - gchar const * sp_object_tagName_get(SPObject const *object, SPException *ex) { @@ -1364,9 +1417,7 @@ sp_object_setAttribute(SPObject *object, gchar const *key, gchar const *value, S g_return_if_fail(SP_EXCEPTION_IS_OK(ex)); /// \todo fixme: Exception if object is NULL? */ - if (!sp_repr_set_attr(object->repr, key, value)) { - ex->code = SP_NO_MODIFICATION_ALLOWED_ERR; - } + object->repr->setAttribute(key, value, false); } void @@ -1376,9 +1427,7 @@ sp_object_removeAttribute(SPObject *object, gchar const *key, SPException *ex) g_return_if_fail(SP_EXCEPTION_IS_OK(ex)); /// \todo fixme: Exception if object is NULL? */ - if (!sp_repr_set_attr(object->repr, key, NULL)) { - ex->code = SP_NO_MODIFICATION_ALLOWED_ERR; - } + object->repr->setAttribute(key, NULL, false); } /* Helper */ @@ -1536,6 +1585,199 @@ sp_object_prev(SPObject *child) return NULL; } +/* Titles and descriptions */ + +/* Note: + Titles and descriptions are stored in 'title' and 'desc' child elements + (see section 5.4 of the SVG 1.0 and 1.1 specifications). The spec allows + an element to have more than one 'title' child element, but strongly + recommends against this and requires using the first one if a choice must + be made. The same applies to 'desc' elements. Therefore, these functions + ignore all but the first 'title' child element and first 'desc' child + element, except when deleting a title or description. +*/ + +/** + * Returns the title of this object, or NULL if there is none. + * The caller must free the returned string using g_free() - see comment + * for getTitleOrDesc() below. + */ +gchar * +SPObject::title() const +{ + return getTitleOrDesc("svg:title"); +} + +/** + * Sets the title of this object + * A NULL first argument is interpreted as meaning that the existing title + * (if any) should be deleted. + * The second argument is optional - see setTitleOrDesc() below for details. + */ +bool +SPObject::setTitle(gchar const *title, bool verbatim) +{ + return setTitleOrDesc(title, "svg:title", verbatim); +} + +/** + * Returns the description of this object, or NULL if there is none. + * The caller must free the returned string using g_free() - see comment + * for getTitleOrDesc() below. + */ +gchar * +SPObject::desc() const +{ + return getTitleOrDesc("svg:desc"); +} + +/** + * Sets the description of this object. + * A NULL first argument is interpreted as meaning that the existing + * description (if any) should be deleted. + * The second argument is optional - see setTitleOrDesc() below for details. + */ +bool +SPObject::setDesc(gchar const *desc, bool verbatim) +{ + return setTitleOrDesc(desc, "svg:desc", verbatim); +} + +/** + * Returns the title or description of this object, or NULL if there is none. + * + * The SVG spec allows 'title' and 'desc' elements to contain text marked up + * using elements from other namespaces. Therefore, this function cannot + * in general just return a pointer to an existing string - it must instead + * construct a string containing the title or description without the mark-up. + * Consequently, the return value is a newly allocated string (or NULL), and + * must be freed (using g_free()) by the caller. + */ +gchar * +SPObject::getTitleOrDesc(gchar const *svg_tagname) const +{ + SPObject *elem = findFirstChild(svg_tagname); + if (elem == NULL) return NULL; + return g_string_free(elem->textualContent(), FALSE); +} + +/** + * Sets or deletes the title or description of this object. + * A NULL 'value' argument causes the title or description to be deleted. + * + * 'verbatim' parameter: + * If verbatim==true, then the title or description is set to exactly the + * specified value. If verbatim==false then two exceptions are made: + * (1) If the specified value is just whitespace, then the title/description + * is deleted. + * (2) If the specified value is the same as the current value except for + * mark-up, then the current value is left unchanged. + * This is usually the desired behaviour, so 'verbatim' defaults to false for + * setTitle() and setDesc(). + * + * The return value is true if a change was made to the title/description, + * and usually false otherwise. + */ +bool +SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool verbatim) +{ + if (!verbatim) { + // If the new title/description is just whitespace, + // treat it as though it were NULL. + if (value) { + bool just_whitespace = true; + for (const gchar *cp = value; *cp; ++cp) { + if (!std::strchr("\r\n \t", *cp)) { + just_whitespace = false; + break; + } + } + if (just_whitespace) value = NULL; + } + // Don't stomp on mark-up if there is no real change. + if (value) { + gchar *current_value = getTitleOrDesc(svg_tagname); + if (current_value) { + bool different = std::strcmp(current_value, value); + g_free(current_value); + if (!different) return false; + } + } + } + + SPObject *elem = findFirstChild(svg_tagname); + + if (value == NULL) { + if (elem == NULL) return false; + // delete the title/description(s) + while (elem) { + elem->deleteObject(); + elem = findFirstChild(svg_tagname); + } + return true; + } + + Inkscape::XML::Document *xml_doc = sp_document_repr_doc(document); + + if (elem == NULL) { + // create a new 'title' or 'desc' element, putting it at the + // beginning (in accordance with the spec's recommendations) + Inkscape::XML::Node *xml_elem = xml_doc->createElement(svg_tagname); + repr->addChild(xml_elem, NULL); + elem = document->getObjectByRepr(xml_elem); + Inkscape::GC::release(xml_elem); + } + else { + // remove the current content of the 'text' or 'desc' element + SPObject *child; + while (NULL != (child = elem->firstChild())) child->deleteObject(); + } + + // add the new content + elem->appendChildRepr(xml_doc->createTextNode(value)); + return true; +} + +/** + * Find the first child of this object with a given tag name, + * and return it. Returns NULL if there is no matching child. + */ +SPObject * +SPObject::findFirstChild(gchar const *tagname) const +{ + for (SPObject *child = children; child; child = child->next) + { + if (child->repr->type() == Inkscape::XML::ELEMENT_NODE && + !strcmp(child->repr->name(), tagname)) return child; + } + return NULL; +} + +/** + * Return the full textual content of an element (typically all the + * content except the tags). + * Must not be used on anything except elements. + */ +GString* +SPObject::textualContent() const +{ + GString* text = g_string_new(""); + + for (const SPObject *child = firstChild(); child; child = child->next) + { + Inkscape::XML::NodeType child_type = child->repr->type(); + + if (child_type == Inkscape::XML::ELEMENT_NODE) { + GString * new_text = child->textualContent(); + g_string_append(text, new_text->str); + g_string_free(new_text, TRUE); + } + else if (child_type == Inkscape::XML::TEXT_NODE) { + g_string_append(text, child->repr->content()); + } + } + return text; +} /* Local Variables: