X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fsp-object.cpp;h=cd90ab7ab494b5063ba58b5a2a8dc073c2ace785;hb=96c274534e3b260291098b7da1875695fe5b30a7;hp=f6ddf27dfa0aae1414a756e7ddf5c2c0ac260e63;hpb=973c92f3bdd6e7877e29e556eff3d9675df1be35;p=inkscape.git diff --git a/src/sp-object.cpp b/src/sp-object.cpp index f6ddf27df..cd90ab7ab 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 @@ -84,7 +85,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 */ @@ -1066,7 +1067,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 +1076,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,10 +1112,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(NULL); // FIXME + repr = SP_OBJECT_REPR(object)->duplicate(doc); if (!( flags & SP_OBJECT_WRITE_EXT )) { repr->setAttribute("inkscape:collect", NULL); } @@ -1179,7 +1180,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; @@ -1195,7 +1196,9 @@ SPObject::updateRepr(unsigned int flags) { * 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 +1207,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 { @@ -1362,36 +1364,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) { @@ -1591,6 +1563,166 @@ 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 or purely whitespace argument is interpreted as meaning that + * the existing title (if any) should be deleted. + */ +void +SPObject::setTitle(gchar const *title) +{ + setTitleOrDesc(title, "svg:title"); +} + +/** + * 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 or purely whitespace argument is interpreted as meaning that + * the existing description (if any) should be deleted. + */ +void +SPObject::setDesc(gchar const *desc) +{ + setTitleOrDesc(desc, "svg:desc"); +} + +/** + * 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. + */ +void +SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname) +{ + SPObject *elem = findFirstChild(svg_tagname); + + // if the new title/description is NULL, or just whitespace, + // then delete any existing title/description + bool just_whitespace = true; + if (value) + for (const gchar *cp = value; *cp; ++cp) { + if (!std::strchr("\r\n \t", *cp)) { + just_whitespace = false; + break; + } + } + if (just_whitespace) { + // delete the title/description(s) + while (elem) { + elem->deleteObject(); + elem = findFirstChild(svg_tagname); + } + return; + } + + 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); + } + 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)); +} + +/** + * 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: