sp-object.cpp revision 420d49b61e68ada5f9029ca1e84d987e8846228f
#define __SP_OBJECT_C__
/** \file
* SPObject implementation.
*
* Authors:
* Lauris Kaplinski <lauris@kaplinski.com>
* bulia byak <buliabyak@users.sf.net>
*
* Copyright (C) 1999-2005 authors
* Copyright (C) 2001-2002 Ximian, Inc.
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
/** \class SPObject
*
* SPObject is an abstract base class of all of the document nodes at the
* SVG document level. Each SPObject subclass implements a certain SVG
* element node type, or is an abstract base class for different node
* types. The SPObject layer is bound to the SPRepr layer, closely
* following the SPRepr mutations via callbacks. During creation,
* SPObject parses and interprets all textual attributes and CSS style
* strings of the SPRepr, and later updates the internal state whenever
* it receives a signal about a change. The opposite is not true - there
* are methods manipulating SPObjects directly and such changes do not
* propagate to the SPRepr layer. This is important for implementation of
* the undo stack, animations and other features.
*
* SPObjects are bound to the higher-level container SPDocument, which
* provides document level functionality such as the undo stack,
* dictionary and so on. Source: doc/architecture.txt
*/
#include "helper/sp-marshal.h"
#include "xml/node-event-vector.h"
#include "attributes.h"
#include "document.h"
#include "style.h"
#include "sp-object-repr.h"
#include "sp-root.h"
#include "streq.h"
#include "strneq.h"
#include "xml/node-fns.h"
#include "debug/event-tracker.h"
#include "algorithms/longest-common-suffix.h"
#define noSP_OBJECT_DEBUG_CASCADE
#define noSP_OBJECT_DEBUG
#ifdef SP_OBJECT_DEBUG
g_print(f, ## a); \
g_print("\n"); \
}
#else
# define debug(f, a...) /**/
#endif
static void sp_object_child_added(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *ref);
static void sp_object_order_changed(SPObject *object, Inkscape::XML::Node *child, Inkscape::XML::Node *old_ref, Inkscape::XML::Node *new_ref);
static Inkscape::XML::Node *sp_object_private_write(SPObject *object, Inkscape::XML::Node *repr, guint flags);
/* Real handlers of repr signals */
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);
static void sp_object_repr_content_changed(Inkscape::XML::Node *repr, gchar const *oldcontent, gchar const *newcontent, gpointer data);
static void sp_object_repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data);
static void sp_object_repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data);
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);
};
static GObjectClass *parent_class;
/**
* Registers the SPObject class with Gdk and returns its type number.
*/
sp_object_get_type(void)
{
if (!type) {
sizeof(SPObjectClass),
sizeof(SPObject),
16,
};
}
return type;
}
/**
* Initializes the SPObject vtable.
*/
static void
{
G_TYPE_NONE, 0);
}
/**
* Callback to initialize the SPObject object.
*/
static void
{
object->_total_hrefcount = 0;
}
/**
* Callback to destroy all members and connections of object and itself.
*/
static void
{
if (spobject->_successor) {
}
}
}
namespace {
return result;
}
return result;
}
public:
{}
} else {
}
}
unsigned propertyCount() const { return 2; }
switch (index) {
case 0:
case 1:
default:
return PropertyPair();
}
}
private:
unsigned _refcount;
};
}
/**
* Increase reference count of object, with possible debugging.
*
* \param owner If non-NULL, make debug log entry.
* \return object, NULL is error.
* \pre object points to real object
*/
SPObject *
{
return object;
}
/**
* Decrease reference count of object, with possible debugging and
* finalization.
*
* \param owner If non-NULL, make debug log entry.
* \return always NULL
* \pre object points to real object
*/
SPObject *
{
return NULL;
}
/**
* Increase weak refcount.
*
* Hrefcount is used for weak references, for example, to
* determine whether any graphical element references a certain gradient
* node.
* \param owner Ignored.
* \return object, NULL is error
* \pre object points to real object
*/
SPObject *
{
return object;
}
/**
* Decrease weak refcount.
*
* Hrefcount is used for weak references, for example, to determine whether
* any graphical element references a certain gradient node.
* \param owner Ignored.
* \return always NULL
* \pre object points to real object and hrefcount>0
*/
SPObject *
{
return NULL;
}
/**
* Adds increment to _total_hrefcount of object and its parents.
*/
void
g_critical("HRefs overcounted");
}
if ( iter->_total_hrefcount == 0 &&
{
}
}
if (topmost_collectable) {
}
}
/**
*/
bool
while (object) {
if ( object == this ) {
return true;
}
}
return false;
}
namespace {
return &a == &b;
}
}
/**
* Returns youngest object being parent to this and object.
*/
SPObject const *
}
return NULL;
return obj;
}
/**
* Compares height of objects in tree.
*
* Works for different-parent objects, so long as they have a common ancestor.
* \return \verbatim
* 0 positions are equivalent
* 1 first object's position is greater than the second
* -1 first object's position is less than the second \endverbatim
*/
int
{
// we have an object and its ancestor (should not happen when sorting selection)
return 1;
return -1;
}
/**
* Append repr as child of this object.
* \pre this is not a cloned object
*/
SPObject *
if (!SP_OBJECT_IS_CLONED(this)) {
} else {
g_critical("Attempt to append repr as child of cloned object");
return NULL;
}
}
/** Gets the label property for the object or a default if no label
* is defined.
*/
gchar const *
return _label;
}
/** Returns a default label property for the object. */
gchar const *
SPObject::defaultLabel() const {
if (_label) {
return _label;
} else {
if (!_default_label) {
if (id) {
} else {
}
}
return _default_label;
}
}
/** Sets the label property for the object */
void
}
/** Queues the object for orphan collection */
void
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?
*/
}
/** Sends the delete signal to all children of this object recursively */
void
}
}
/**
* Deletes the object reference, unparenting it from its parent.
*
* If the \a propagate parameter is set to true, it emits a delete
* signal. If the \a propagate_descendants parameter is true, it
* recursively sends the delete signal to children.
*/
void
{
sp_object_ref(this, NULL);
if (propagate) {
_delete_signal.emit(this);
}
if (propagate_descendants) {
this->_sendDeleteSignalRecursive();
}
}
if (_successor) {
}
sp_object_unref(this, NULL);
}
/**
* Put object into object tree, under parent, and behind prev;
* also update object's XML space.
*/
void
{
if (prev) {
} else {
}
if (!next) {
}
}
/**
* In list of object's siblings, move object behind prev.
*/
void
{
}
if (old_prev) {
} else {
}
if (!next) {
}
if (prev) {
} else {
}
if (!next) {
}
}
/**
* Remove object from parent's children, release and unref it.
*/
void
{
}
if (prev) {
} else {
}
if (!next) {
}
}
/**
* Return object's child whose node pointer equals repr.
*/
SPObject *
{
return child;
}
}
return NULL;
}
/**
* Callback for child_added event.
* Invoked whenever the given mutation event happens in the XML tree.
*/
static void
{
if (!type) {
return;
}
}
/**
* Removes, releases and unrefs all children of object.
*
* This is the opposite of build. It has to be invoked as soon as the
* object is removed from the tree, even if it is still alive according
* to reference count. The frontend unregisters the object from the
* document and releases the SPRepr bindings; implementations should free
* state data and release all child objects. Invoking release on
* SPRoot destroys the whole document tree.
* \see sp_object_build()
*/
{
}
}
/**
* Remove object's child whose node equals repr, release and
* unref it.
*
* Invoked whenever the given mutation event happens in the XML
* tree, BEFORE removal from the XML tree happens, so grouping
* objects can safely release the child data.
*/
static void
{
}
/**
* Move object corresponding to child after sibling object corresponding
* to new_ref.
* 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,
{
}
/**
* Virtual build callback.
*
* This has to be invoked immediately after creation of an SPObject. The
* frontend method ensures that the new object is properly attached to
* the document and repr; implementation then will parse all of the attributes,
* generate the children objects and so on. Invoking build on the SPRoot
* object results in creation of the whole document tree (this is, what
* SPDocument does after the creation of the XML tree).
* \see sp_object_release()
*/
static void
{
/* Nothing specific here */
if (!type) {
continue;
}
}
}
void
sp_object_invoke_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned)
{
/* Bookkeeping */
if (!SP_OBJECT_IS_CLONED(object)) {
/* If we are not cloned, force unique id */
/* Redefine ID, if required */
}
}
} else {
}
/* Invoke derived methods, if any */
}
/* Signalling (should be connected AFTER processing derived methods */
}
void
{
// we need to remember our parent
// g_assert(!object->parent);
/* all hrefs should be released by the "release" handlers */
if (!SP_OBJECT_IS_CLONED(object)) {
}
} else {
}
}
}
/**
* 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)
{
}
/**
* 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)
{
}
}
/**
* Callback for order_changed node event.
*
* \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)
{
}
}
/**
* Callback for set event.
*/
static void
{
switch (key) {
case SP_ATTR_ID:
if (value) {
}
// give the conflicting object a new ID
}
}
if (value) {
} else {
}
}
break;
case SP_ATTR_INKSCAPE_LABEL:
if (value) {
} else {
}
break;
case SP_ATTR_INKSCAPE_COLLECT:
} else {
}
break;
case SP_ATTR_XML_SPACE:
}
break;
default:
break;
}
}
/**
* Call virtual set() function of object.
*/
void
{
}
}
/**
* Read value of key attribute from XML node into object.
*/
void
{
if (keyid != SP_ATTR_INVALID) {
/* Retrieve the 'key' attribute from the object's XML representation */
}
}
/**
* 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)
{
// manual changes to extension attributes require the normal
// attributes, which depend on them, to be updated immediately
if (is_interactive) {
}
}
/**
* 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)
{
}
/**
* Return string representation of space value.
*/
static gchar const*
sp_xml_get_space_string(unsigned int space)
{
switch (space) {
case SP_XML_SPACE_DEFAULT:
return "default";
case SP_XML_SPACE_PRESERVE:
return "preserve";
default:
return NULL;
}
}
/**
* Callback for write event.
*/
{
if (!( flags & SP_OBJECT_WRITE_EXT )) {
}
} else {
char const *xml_space;
}
if ( flags & SP_OBJECT_WRITE_EXT &&
{
} else {
}
}
return repr;
}
/**
* Update this object's XML node with flags value.
*/
if (!SP_OBJECT_IS_CLONED(this)) {
if (repr) {
} else {
g_critical("Attempt to update non-existent repr");
return NULL;
}
} else {
/* cloned objects have no repr */
return NULL;
}
}
if (SP_OBJECT_IS_CLONED(this)) {
/* cloned objects have no repr */
return NULL;
}
repr = SP_OBJECT_REPR(this);
}
} else {
if (!repr) {
if (flags & SP_OBJECT_WRITE_BUILD) {
}
/// \todo fixme: else probably error (Lauris) */
} else {
}
return repr;
}
}
/* Modification */
/**
* Add \a flags to \a object's as dirtiness flags, and
* recursively add CHILD_MODIFIED flag to
* parent and ancestors (as far up as necessary).
*/
void
{
if (update_in_progress) {
}
/* 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)));
/* Just set this flags safe even if some have been set before */
if (propagate) {
if (this->parent) {
} else {
sp_document_request_modified(this->document);
}
}
}
void
{
#ifdef SP_OBJECT_DEBUG_CASCADE
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);
#endif
/* Get this flags */
/* Copy flags to modified cascade for later processing */
/* We have to clear flags here to allow rescheduling update */
this->uflags = 0;
// Merge style if we have good reasons to think that parent style is changed */
/** \todo
* I am not sure whether we should check only propagated
* flag. We are currently assuming that style parsing is
* done immediately. I think this is correct (Lauris).
*/
}
}
}
void
{
/* PARENT_MODIFIED is computed later on and is not intended to be
* "manually" queued */
/* we should be setting either MODIFIED or CHILD_MODIFIED... */
/* ...but not both */
unsigned int old_mflags=this->mflags;
/* 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 (parent) {
} else {
}
}
}
void
{
/* only the MODIFIED_CASCADE flag is legal here */
#ifdef SP_OBJECT_DEBUG_CASCADE
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);
#endif
/* We have to clear mflags beforehand, as signal handlers may
* make changes and therefore queue new modification notifications
* themselves. */
this->mflags = 0;
g_object_ref(G_OBJECT(this));
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 *
{
return NULL;
}
gchar const *
{
return NULL;
}
unsigned int
{
return FALSE;
}
unsigned int
{
return FALSE;
}
gchar const *
{
/* If exception is not clear, return */
if (!SP_EXCEPTION_IS_OK(ex)) {
return NULL;
}
/// \todo fixme: Exception if object is NULL? */
}
gchar const *
{
/* If exception is not clear, return */
if (!SP_EXCEPTION_IS_OK(ex)) {
return NULL;
}
/// \todo fixme: Exception if object is NULL? */
}
void
{
/* If exception is not clear, return */
/// \todo fixme: Exception if object is NULL? */
}
}
void
{
/* If exception is not clear, return */
/// \todo fixme: Exception if object is NULL? */
}
}
/* Helper */
static gchar *
{
static unsigned long count = 0;
count++;
if (local) {
}
}
}
do {
++count;
return buf;
}
/* Style */
/**
* Returns an object style property.
*
* \todo
* fixme: Use proper CSS parsing. The current version is buggy
* in a number of situations where key is a substring of the
* style string other than as a property name (including
* where key is a substring of a property name), and is also
* buggy in its handling of inheritance for properties that
* aren't inherited by default. It also doesn't allow for
* the case where the property is specified but with an invalid
* value (in which case I believe the CSS2 error-handling
* behaviour applies, viz. behave as if the property hadn't
* been specified). Also, the current code doesn't use CRSelEng
* stuff to take a value from stylesheets. Also, we aren't
* setting any hooks to force an update for changes in any of
* the inputs (i.e., in any of the elements that this function
* queries).
*
* \par
* Given that the default value for a property depends on what
* property it is (e.g., whether to inherit or not), and given
* the above comment about ignoring invalid values, and that the
* repr parent isn't necessarily the right element to inherit
* from (e.g., maybe we need to inherit from the referencing
* <use> element instead), we should probably make the caller
* responsible for ascending the repr tree as necessary.
*/
gchar const *
{
if (style) {
char const *p;
!= NULL )
{
p += len;
while ((*p <= ' ') && *p) p++;
if (*p++ != ':') break;
while ((*p <= ' ') && *p) p++;
if (*p
&& (p[inherit_len] == '\0'
|| p[inherit_len] == ';'
|| g_ascii_isspace(p[inherit_len])))) {
return p;
}
}
}
return val;
}
}
return def;
}
/**
* Lifts SVG version of all root objects to version.
*/
void
if (SP_IS_ROOT(object)) {
}
}
}
}
/**
* Return sodipodi version of first root ancestor or (0,0).
*/
{
while (object) {
if (SP_IS_ROOT(object)) {
}
}
return zero_version;
}
/**
* Returns previous object in sibling list or NULL.
*/
SPObject *
{
if (SP_OBJECT_NEXT(i) == child)
return i;
}
return NULL;
}
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :