sp-object.cpp revision 5636d85352f341e05629c369f023eeb1a1457cd8
#define __SP_OBJECT_C__
/** \file
* SPObject implementation.
*
* Authors:
* Lauris Kaplinski <lauris@kaplinski.com>
* bulia byak <buliabyak@users.sf.net>
* Stephen Silver <sasilver@users.sourceforge.net>
*
* Copyright (C) 1999-2008 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 <cstring>
#include <string>
#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 "sp-style-elem.h"
#include "sp-script.h"
#include "streq.h"
#include "strneq.h"
#include "xml/node-fns.h"
#include "debug/event-tracker.h"
#include "debug/simple-event.h"
#include "debug/demangle.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::Document *doc, 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
{
}
/**
* Callback to initialize the SPObject object.
*/
static void
{
object->_total_hrefcount = 0;
// 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
}
/**
* Callback to destroy all members and connections of object and itself.
*/
static void
{
if (spobject->_successor) {
}
}
}
namespace {
class RefCountEvent : public BaseRefCountEvent {
public:
{
}
};
class RefEvent : public RefCountEvent {
public:
{}
};
class UnrefEvent : public RefCountEvent {
public:
{}
};
}
/**
* 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;
}
}
/**
* Retrieves the children as a GSList object, optionally ref'ing the children
* in the process, if add_ref is specified.
*/
for (SPObject *child = sp_object_first_child(this) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
if (add_ref)
l = g_slist_prepend (l, child);
}
return l;
}
/** 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
// 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
* 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
{
if (ochild)
}
/**
* 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 (!cloned)
if (!SP_OBJECT_IS_CLONED(object)) {
/* If we are not cloned, and not seeking, force unique id */
/* Redefine ID, if required */
}
} else if (id) {
// bind if id, but no conflict -- otherwise, we can expect
// a subsequent setting of the id attribute
}
}
}
} else {
}
/* Invoke derived methods, if any */
}
/* Signalling (should be connected AFTER processing derived methods */
}
void SPObject::releaseReferences() {
sp_repr_remove_listener_by_data(this->repr, this);
this->_release_signal.emit(this);
}
/* all hrefs should be released by the "release" handlers */
if (!SP_OBJECT_IS_CLONED(this)) {
if (this->id) {
}
g_free(this->_default_label);
this->_default_label = NULL;
} else {
}
if (this->style) {
}
}
/**
* 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 (new_id) {
}
// give the conflicting object a new ID
} else {
}
}
}
if (new_id) {
} else {
}
}
break;
case SP_ATTR_INKSCAPE_LABEL:
if (value) {
} else {
}
break;
case SP_ATTR_INKSCAPE_COLLECT:
} else {
}
break;
case SP_ATTR_XML_SPACE:
}
break;
case SP_ATTR_STYLE:
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) {
object->updateRepr(0);
}
}
/**
* 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.
*/
sp_object_private_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags)
{
if (!( flags & SP_OBJECT_WRITE_EXT )) {
}
} else {
char const *xml_space;
}
if ( flags & SP_OBJECT_WRITE_EXT &&
{
} else {
}
if (obj_style) {
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.
*/
if (!style_str) {
style_str = "NULL";
}
}
/** \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.
*/
}
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;
}
}
/** 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"
*/
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) {
}
/* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
* SP_OBJECT_CHILD_MODIFIED_FLAG */
bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | 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) {
if (parent) {
} else {
}
}
}
/**
* Update views
*/
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).
*/
}
}
try
{
}
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);");
}
}
/**
* 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
{
/* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or
* SP_OBJECT_CHILD_MODIFIED_FLAG */
bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | 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) {
if (parent) {
} else {
}
}
}
/**
* 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
{
/* 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));
}
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;
}
/* 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 *
{
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
{
}
/**
* 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 *
{
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
{
}
/**
* 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 *
{
}
/**
* 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
{
if (!verbatim) {
// If the new title/description is just whitespace,
// treat it as though it were NULL.
if (value) {
bool just_whitespace = true;
just_whitespace = false;
break;
}
}
}
// Don't stomp on mark-up if there is no real change.
if (value) {
if (current_value) {
if (!different) return false;
}
}
}
// delete the title/description(s)
while (elem) {
elem->deleteObject();
}
return true;
}
// create a new 'title' or 'desc' element, putting it at the
// beginning (in accordance with the spec's recommendations)
}
else {
// remove the current content of the 'text' or 'desc' element
}
// add the new content
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 *
{
{
}
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.
*/
SPObject::textualContent() const
{
{
}
}
}
return text;
}
/*
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 :