X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fsp-object.cpp;h=fd17b3c12f521daeb688391ae93946cbc7aefe6c;hb=8cd8b6ee0c2bd47add62310866479d49a2070057;hp=99d73970061b9389ebdca9848c2bb8b5814991ac;hpb=66632b492f9cd54e5667fd4e1fca8e457f59b282;p=inkscape.git diff --git a/src/sp-object.cpp b/src/sp-object.cpp index 99d739700..fd17b3c12 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 @@ -37,10 +38,13 @@ #include "helper/sp-marshal.h" #include "xml/node-event-vector.h" #include "attributes.h" +#include "color-profile-fns.h" #include "document.h" #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" @@ -50,8 +54,8 @@ #include "debug/demangle.h" #include "util/share.h" #include "util/format.h" +#include "util/longest-common-suffix.h" -#include "algorithms/longest-common-suffix.h" using std::memcpy; using std::strchr; using std::strcmp; @@ -84,7 +88,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 */ @@ -109,6 +113,37 @@ Inkscape::XML::NodeEventVector object_event_vector = { sp_object_repr_order_changed }; +// A friend class used to set internal members on SPObject so as to not expose settors in SPObject's public API +class SPObjectImpl +{ +public: + +/** + * Null's the id member of an SPObject without attempting to free prior contents. + */ + static void setIdNull( SPObject* obj ) { + if (obj) { + obj->id = 0; + } + } + +/** + * Sets the id member of an object, freeing any prior content. + */ + static void setId( SPObject* obj, gchar const* id ) { + if (obj && (id != obj->id) ) { + if (obj->id) { + g_free(obj->id); + obj->id = 0; + } + if (id) { + obj->id = g_strdup(id); + } + } + } +}; + + static GObjectClass *parent_class; /** @@ -174,7 +209,7 @@ sp_object_init(SPObject *object) object->children = object->_last_child = NULL; object->parent = object->next = NULL; object->repr = NULL; - object->id = NULL; + SPObjectImpl::setIdNull(object); object->_collection_policy = SPObject::COLLECT_WITH_PARENT; @@ -185,8 +220,8 @@ sp_object_init(SPObject *object) 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, + // 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); @@ -256,6 +291,10 @@ public: } +gchar const* SPObject::getId() const { + return id; +} + /** * Increase reference count of object, with possible debugging. * @@ -501,22 +540,33 @@ SPObject::setLabel(gchar const *label) { /** Queues the object for orphan collection */ -void -SPObject::requestOrphanCollection() { +void SPObject::requestOrphanCollection() { g_return_if_fail(document != NULL); - document->queueForOrphanCollection(this); - /** \todo - * This is a temporary hack added to make fill&stroke rebuild its - * gradient list when the defs are vacuumed. gradient-vector.cpp - * listens to the modified signal on defs, and now we give it that - * signal. Mental says that this should be made automatic by - * merging SPObjectGroup with SPObject; SPObjectGroup would issue - * this signal automatically. Or maybe just derive SPDefs from - * SPObjectGroup? - */ + // do not remove style or script elements (Bug #276244) + if (SP_IS_STYLE_ELEM(this)) { + // leave it + } else if (SP_IS_SCRIPT(this)) { + // leave it + } else if (SP_IS_PAINT_SERVER(this) && static_cast(this)->isSwatch() ) { + // leave it + } else if (IS_COLORPROFILE(this)) { + // leave it + } else { + document->queueForOrphanCollection(this); + + /** \todo + * This is a temporary hack added to make fill&stroke rebuild its + * gradient list when the defs are vacuumed. gradient-vector.cpp + * listens to the modified signal on defs, and now we give it that + * signal. Mental says that this should be made automatic by + * merging SPObjectGroup with SPObject; SPObjectGroup would issue + * this signal automatically. Or maybe just derive SPDefs from + * SPObjectGroup? + */ - this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG); + this->requestModified(SP_OBJECT_CHILD_MODIFIED_FLAG); + } } /** Sends the delete signal to all children of this object recursively */ @@ -798,8 +848,7 @@ sp_object_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *rep } } -void -sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned) +void sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned) { debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); @@ -810,13 +859,14 @@ sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::No g_assert(object->document == NULL); g_assert(object->repr == NULL); - g_assert(object->id == NULL); + g_assert(object->getId() == NULL); /* Bookkeeping */ 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)) { @@ -826,27 +876,30 @@ sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::No /* If we are not cloned, and not seeking, force unique id */ gchar const *id = object->repr->attribute("id"); if (!document->isSeeking()) { - gchar *realid = sp_object_get_unique_id(object, id); - g_assert(realid != NULL); + { + gchar *realid = sp_object_get_unique_id(object, id); + g_assert(realid != NULL); - object->document->bindObjectToId(realid, object); - object->id = realid; + object->document->bindObjectToId(realid, object); + SPObjectImpl::setId(object, realid); + g_free(realid); + } /* Redefine ID, if required */ - if ((id == NULL) || (strcmp(id, realid) != 0)) { - object->repr->setAttribute("id", realid); + if ((id == NULL) || (strcmp(id, object->getId()) != 0)) { + object->repr->setAttribute("id", object->getId()); } } 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); + SPObjectImpl::setId(object, id); } } } } else { - g_assert(object->id == NULL); + g_assert(object->getId() == NULL); } /* Invoke derived methods, if any */ @@ -884,6 +937,8 @@ void SPObject::releaseReferences() { this->_default_label = NULL; this->document->bindObjectToRepr(this->repr, NULL); + + Inkscape::GC::release(this->repr); } else { g_assert(!this->id); } @@ -892,12 +947,21 @@ void SPObject::releaseReferences() { this->style = sp_style_unref(this->style); } - Inkscape::GC::release(this->repr); - this->document = NULL; this->repr = NULL; } + +SPObject *SPObject::getNext() +{ + return next; +} + +SPObject *SPObject::getPrev() +{ + return sp_object_prev(this); +} + /** * Callback for child_added node event. */ @@ -971,16 +1035,14 @@ sp_object_private_set(SPObject *object, unsigned int key, gchar const *value) } } - if (object->id) { - document->bindObjectToId(object->id, NULL); - g_free(object->id); + if (object->getId()) { + document->bindObjectToId(object->getId(), NULL); + SPObjectImpl::setId(object, 0); } if (new_id) { - object->id = g_strdup((char const*)new_id); - document->bindObjectToId(object->id, object); - } else { - object->id = NULL; + SPObjectImpl::setId(object, new_id); + document->bindObjectToId(object->getId(), object); } g_free(object->_default_label); @@ -1066,7 +1128,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); @@ -1075,7 +1137,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); } } @@ -1111,15 +1173,15 @@ 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(NULL); // FIXME + repr = SP_OBJECT_REPR(object)->duplicate(doc); if (!( flags & SP_OBJECT_WRITE_EXT )) { repr->setAttribute("inkscape:collect", NULL); } } else { - repr->setAttribute("id", object->id); + repr->setAttribute("id", object->getId()); if (object->xml_space.set) { char const *xml_space; @@ -1134,7 +1196,7 @@ 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); @@ -1179,7 +1241,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; @@ -1190,12 +1252,14 @@ SPObject::updateRepr(unsigned int flags) { } } -/** Used both to create reprs in the original document, and to create +/** 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; @@ -1204,13 +1268,12 @@ 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) { - /// \todo FIXME: Plumb an appropriate XML::Document into this - repr = SP_OBJECT_REPR(this)->duplicate(NULL); + repr = SP_OBJECT_REPR(this)->duplicate(doc); } /// \todo FIXME: else probably error (Lauris) */ } else { @@ -1292,15 +1355,27 @@ 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 + * 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 @@ -1331,9 +1406,9 @@ 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 + * 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. */ @@ -1362,36 +1437,6 @@ SPObject::emitModified(unsigned int 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) { @@ -1423,9 +1468,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 @@ -1435,9 +1478,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 */ @@ -1564,37 +1605,212 @@ SPObject::_requireSVGVersion(Inkscape::Version version) { } /** - * Return sodipodi version of first root ancestor or (0,0). + * Returns previous object in sibling list or NULL. */ -Inkscape::Version -sp_object_get_sodipodi_version(SPObject *object) +SPObject * +sp_object_prev(SPObject *child) { - static Inkscape::Version const zero_version(0, 0); + SPObject *parent = SP_OBJECT_PARENT(child); + for ( SPObject *i = sp_object_first_child(parent); i; i = SP_OBJECT_NEXT(i) ) { + if (SP_OBJECT_NEXT(i) == child) + return i; + } + return NULL; +} - while (object) { - if (SP_IS_ROOT(object)) { - return SP_ROOT(object)->version.sodipodi; +/* 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; } - object = SP_OBJECT_PARENT(object); + // 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(); } - return zero_version; + // add the new content + elem->appendChildRepr(xml_doc->createTextNode(value)); + return true; } /** - * Returns previous object in sibling list or NULL. + * Find the first child of this object with a given tag name, + * and return it. Returns NULL if there is no matching child. */ SPObject * -sp_object_prev(SPObject *child) +SPObject::findFirstChild(gchar const *tagname) const { - SPObject *parent = SP_OBJECT_PARENT(child); - for ( SPObject *i = sp_object_first_child(parent); i; i = SP_OBJECT_NEXT(i) ) { - if (SP_OBJECT_NEXT(i) == child) - return i; + 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: