// SPDX-License-Identifier: GPL-2.0-or-later #ifndef SP_OBJECT_H_SEEN #define SP_OBJECT_H_SEEN /* * Authors: * Lauris Kaplinski * Jon A. Cruz * Abhishek Sharma * Adrian Boguszewski * * Copyright (C) 1999-2016 authors * Copyright (C) 2001-2002 Ximian, Inc. * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include #include "util/const_char_ptr.h" /* SPObject flags */ class SPObject; #define MAKE_SP_OBJECT_DOWNCAST_FUNCTIONS(func, T) \ inline T *func(SPObject *obj) { return dynamic_cast(obj); } \ inline T const *func(SPObject const *obj) { return dynamic_cast(obj); } \ inline T *func(T *derived) = delete; \ inline T const *func(T const *derived) = delete; #define MAKE_SP_OBJECT_TYPECHECK_FUNCTIONS(func, T) \ inline bool func(SPObject const *obj) { return dynamic_cast(obj); } #define SP_IS_OBJECT(obj) (dynamic_cast(obj) != nullptr) /* Async modification flags */ #define SP_OBJECT_MODIFIED_FLAG (1 << 0) #define SP_OBJECT_CHILD_MODIFIED_FLAG (1 << 1) #define SP_OBJECT_PARENT_MODIFIED_FLAG (1 << 2) #define SP_OBJECT_STYLE_MODIFIED_FLAG (1 << 3) #define SP_OBJECT_VIEWPORT_MODIFIED_FLAG (1 << 4) #define SP_OBJECT_USER_MODIFIED_FLAG_A (1 << 5) #define SP_OBJECT_USER_MODIFIED_FLAG_B (1 << 6) #define SP_OBJECT_STYLESHEET_MODIFIED_FLAG (1 << 7) /* Convenience */ #define SP_OBJECT_FLAGS_ALL 0xff /* Flags that mark object as modified */ /* Object, Child, Style, Viewport, User */ #define SP_OBJECT_MODIFIED_STATE (SP_OBJECT_FLAGS_ALL & ~(SP_OBJECT_PARENT_MODIFIED_FLAG)) /* Flags that will propagate downstreams */ /* Parent, Style, Viewport, User */ #define SP_OBJECT_MODIFIED_CASCADE (SP_OBJECT_FLAGS_ALL & ~(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)) /* Write flags */ #define SP_OBJECT_WRITE_BUILD (1 << 0) #define SP_OBJECT_WRITE_EXT (1 << 1) #define SP_OBJECT_WRITE_ALL (1 << 2) #define SP_OBJECT_WRITE_NO_CHILDREN (1 << 3) #include #include #include #include #include #include #include #include "2geom/point.h" // Used for dpi only #include "version.h" #include "util/forward-pointer-iterator.h" enum class SPAttr; class SPCSSAttr; class SPStyle; namespace Inkscape { namespace XML { class Node; struct Document; } } namespace Glib { class ustring; } /// Unused struct SPCtx { unsigned int flags; }; enum { SP_XML_SPACE_DEFAULT, SP_XML_SPACE_PRESERVE }; class SPDocument; /// Internal class consisting of two bits. class SPIXmlSpace { public: SPIXmlSpace(): set(0), value(SP_XML_SPACE_DEFAULT) {}; unsigned int set : 1; unsigned int value : 1; }; /* * Refcounting * * Owner is here for debug reasons, you can set it to NULL safely * Ref should return object, NULL is error, unref return always NULL */ /** * 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 * @todo need to move this to be a member of SPObject. */ SPObject *sp_object_ref(SPObject *object, SPObject *owner=nullptr); /** * 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 * @todo need to move this to be a member of SPObject. */ SPObject *sp_object_unref(SPObject *object, SPObject *owner=nullptr); /** * 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 */ class SPObject { public: enum CollectionPolicy { COLLECT_WITH_PARENT, ALWAYS_COLLECT }; SPObject(); virtual ~SPObject(); unsigned int cloned : 1; SPObject *clone_original; unsigned int uflags : 8; unsigned int mflags : 8; SPIXmlSpace xml_space; Glib::ustring lang; unsigned int hrefcount; /* number of xlink:href references */ unsigned int _total_hrefcount; /* our hrefcount + total descendants */ SPDocument *document; /* Document we are part of */ SPObject *parent; /* Our parent (only one allowed) */ private: SPObject(const SPObject&); SPObject& operator=(const SPObject&); char *id; /* Our very own unique id */ Inkscape::XML::Node *repr; /* Our xml representation */ public: int refCount; std::list hrefList; /** * Returns the objects current ID string. */ char const* getId() const; void getIds(std::set &ret) const; /** * Get the id in a URL format. */ std::string getUrl() const; /** * Returns the XML representation of tree */ //protected: Inkscape::XML::Node * getRepr(); /** * Returns the XML representation of tree */ Inkscape::XML::Node const* getRepr() const; public: /** * Cleans up an SPObject, releasing its references and * requesting that references to it be released */ void releaseReferences(); /** * Connects to the release request signal * * @param slot the slot to connect * * @return the sigc::connection formed */ sigc::connection connectRelease(sigc::slot slot) { return _release_signal.connect(slot); } /** * Represents the style properties, whether from presentation attributes, the style * attribute, or inherited. * * private_set() doesn't handle SPAttr::STYLE or any presentation attributes at the * time of writing, so this is probably NULL for all SPObject's that aren't an SPItem. * * However, this gives rise to the bugs mentioned in sp_object_get_style_property. * Note that some non-SPItem SPObject's, such as SPStop, do need styling information, * and need to inherit properties even through other non-SPItem parents like \. */ SPStyle *style; /** * Represents the style that should be used to resolve 'context-fill' and 'context-stroke' */ SPStyle *context_style; /// Switch containing next() method. struct ParentIteratorStrategy { static SPObject const *next(SPObject const *object) { return object->parent; } }; typedef Inkscape::Util::ForwardPointerIterator ParentIterator; typedef Inkscape::Util::ForwardPointerIterator ConstParentIterator; bool isSiblingOf(SPObject const *object) const { if (object == nullptr) return false; return this->parent && this->parent == object->parent; } /** * True if object is non-NULL and this is some in/direct parent of object. */ bool isAncestorOf(SPObject const *object) const; /** * Returns youngest object being parent to this and object. */ SPObject const *nearestCommonAncestor(SPObject const *object) const; /* Returns next object in sibling list or NULL. */ SPObject *getNext(); /** * Returns previous object in sibling list or NULL. */ SPObject *getPrev(); bool hasChildren() const { return ( children.size() > 0 ); } SPObject *firstChild() { return children.empty() ? nullptr : &children.front(); } SPObject const *firstChild() const { return children.empty() ? nullptr : &children.front(); } SPObject *lastChild() { return children.empty() ? nullptr : &children.back(); } SPObject const *lastChild() const { return children.empty() ? nullptr : &children.back(); } SPObject *nthChild(unsigned index); SPObject const *nthChild(unsigned index) const; enum Action { ActionGeneral, ActionBBox, ActionUpdate, ActionShow }; /** * Retrieves the children as a std vector object, optionally ref'ing the children * in the process, if add_ref is specified. */ std::vector childList(bool add_ref, Action action = ActionGeneral); /** * Retrieves a list of ancestors of the object, as an easy to use vector * @param root_to_tip - If set, orders the list from the svg root to the tip. */ std::vector ancestorList(bool root_to_tip); /** * Append repr as child of this object. * \pre this is not a cloned object */ SPObject *appendChildRepr(Inkscape::XML::Node *repr); /** * Gets the author-visible label property for the object or a default if * no label is defined. */ char const *label() const; /** * Returns a default label property for this object. */ char const *defaultLabel() const; /** * Sets the author-visible label for this object. * * @param label the new label. */ void setLabel(char const *label); /** * 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. */ char *title() const; /** * 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 setTitle(char const *title, bool verbatim = false); /** * 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. */ char *desc() const; /** * 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 setDesc(char const *desc, bool verbatim=false); /** * Get and set the exportable filename on this object. Usually sp-item or sp-page */ Glib::ustring getExportFilename() const; void setExportFilename(Glib::ustring filename); /** * Get and set the exported DPI for this objet, if available. */ Geom::Point getExportDpi() const; void setExportDpi(Geom::Point dpi); /** * Set the policy under which this object will be orphan-collected. * * Orphan-collection is the process of deleting all objects which no longer have * hyper-references pointing to them. The policy determines when this happens. Many objects * should not be deleted simply because they are no longer referred to; other objects (like * "intermediate" gradients) are more or less throw-away and should always be collected when no * longer in use. * * Along these lines, there are currently two orphan-collection policies: * * COLLECT_WITH_PARENT - don't worry about the object's hrefcount; * if its parent is collected, this object * will be too * * COLLECT_ALWAYS - always collect the object as soon as its * hrefcount reaches zero * * @return the current collection policy in effect for this object */ CollectionPolicy collectionPolicy() const { return _collection_policy; } /** * Sets the orphan-collection policy in effect for this object. * * @param policy the new policy to adopt * * @see SPObject::collectionPolicy */ void setCollectionPolicy(CollectionPolicy policy) { _collection_policy = policy; } /** * Requests a later automatic call to collectOrphan(). * * This method requests that collectOrphan() be called during the document update cycle, * deleting the object if it is no longer used. * * If the current collection policy is COLLECT_WITH_PARENT, this function has no effect. * * @see SPObject::collectOrphan */ void requestOrphanCollection(); /** * Unconditionally delete the object if it is not referenced. * * Unconditionally delete the object if there are no outstanding hyper-references to it. * Observers are not notified of the object's deletion (at the SPObject level; XML tree * notifications still fire). * * @see SPObject::deleteObject */ void collectOrphan() { if ( _total_hrefcount == 0 ) { deleteObject(false); } } /** * Increase weak refcount. * * Hrefcount is used for weak references, for example, to * determine whether any graphical element references a certain gradient * node. * It keeps a list of "owners". * @param owner Used to track who uses this object. */ void hrefObject(SPObject* owner = nullptr); /** * Decrease weak refcount. * * Hrefcount is used for weak references, for example, to determine whether * any graphical element references a certain gradient node. * @param owner Used to track who uses this object. * \pre hrefcount>0 */ void unhrefObject(SPObject* owner = nullptr); /** * Check if object is referenced by any other object. */ bool isReferenced() { return ( _total_hrefcount > 0 ); } /** * Deletes an object, unparenting it from its parent. * * Detaches the object's repr, and optionally sends notification that the object has been * deleted. * * @param propagate If it is set to true, it emits a delete signal. * * @param propagate_descendants If it is true, it recursively sends the delete signal to children. */ void deleteObject(bool propagate, bool propagate_descendants); /** * Deletes on object. * * @param propagate Notify observers of this object and its children that they have been * deleted? */ void deleteObject(bool propagate = true) { deleteObject(propagate, propagate); } /** * Removes all children except for the given object, it's children and it's ancesstors. */ void cropToObject(SPObject *except); void cropToObjects(std::vector except_objects); /** * Connects a slot to be called when an object is deleted. * * This connects a slot to an object's internal delete signal, which is invoked when the object * is deleted * * The signal is mainly useful for e.g. knowing when to break hrefs or dissociate clones. * * @param slot the slot to connect * * @see SPObject::deleteObject */ sigc::connection connectDelete(sigc::slot slot) { return _delete_signal.connect(slot); } sigc::connection connectPositionChanged(sigc::slot slot) { return _position_changed_signal.connect(slot); } /** * Returns the object which supercedes this one (if any). * * This is mainly useful for ensuring we can correctly perform a series of moves or deletes, * even if the objects in question have been replaced in the middle of the sequence. */ SPObject *successor() { return _successor; } /** * Indicates that another object supercedes this one. */ void setSuccessor(SPObject *successor) { assert(successor != NULL); assert(_successor == NULL); assert(successor->_successor == NULL); sp_object_ref(successor, nullptr); _successor = successor; } /* modifications; all three sets of methods should probably ultimately be protected, as they * are not really part of its public interface. However, other parts of the code to * occasionally use them at present. */ /* the no-argument version of updateRepr() is intended to be a bit more public, however -- it * essentially just flushes any changes back to the backing store (the repr layer); maybe it * should be called something else and made public at that point. */ /** * Updates the object's repr based on the object's state. * * This method updates the repr attached to the object to reflect the object's current * state; see the three-argument version for details. * * @param flags object write flags that apply to this update * * @return the updated repr */ Inkscape::XML::Node *updateRepr(unsigned int flags = SP_OBJECT_WRITE_EXT); /** * Updates the given repr based on the object's state. * * 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". * * This method updates the given repr to reflect the object's current state. There are * several flags that affect this: * * SP_OBJECT_WRITE_BUILD - create new reprs * * SP_OBJECT_WRITE_EXT - write elements and attributes * which are not part of pure SVG * (i.e. the Inkscape and Sodipodi * namespaces) * * SP_OBJECT_WRITE_ALL - create all nodes and attributes, * even those which might be redundant * * @param repr the repr to update * @param flags object write flags that apply to this update * * @return the updated repr */ Inkscape::XML::Node *updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags); /** * Queues an deferred update of this object's display. * * This method sets flags to indicate updates to be performed later, during the idle loop. * * There are several flags permitted here: * * SP_OBJECT_MODIFIED_FLAG - the object has been modified * * SP_OBJECT_CHILD_MODIFIED_FLAG - a child of the object has been * modified * * SP_OBJECT_STYLE_MODIFIED_FLAG - the object's style has been * modified * * There are also some subclass-specific modified flags which are hardly ever used. * * One of either MODIFIED or CHILD_MODIFIED is required. * * @param flags flags indicating what to update */ void requestDisplayUpdate(unsigned int flags); /** * Updates the object's display immediately * * This method is called during the idle loop by SPDocument in order to update the object's * display. * * One additional flag is legal here: * * SP_OBJECT_PARENT_MODIFIED_FLAG - the parent has been * modified * * @param ctx an SPCtx which accumulates various state * during the recursive update -- beware! some * subclasses try to cast this to an SPItemCtx * * * @param flags flags indicating what to update (in addition * to any already set flags) */ void updateDisplay(SPCtx *ctx, unsigned int flags); /** * Requests that a modification notification signal * be emitted later (e.g. during the idle loop) * * 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... * * @param flags flags indicating what has been modified */ void 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. * * @param flags indicating what has been modified. */ void emitModified(unsigned int flags); /** * Connects to the modification notification signal * * @param slot the slot to connect * * @return the connection formed thereby */ sigc::connection connectModified( sigc::slot slot ) { return _modified_signal.connect(slot); } /** Sends the delete signal to all children of this object recursively */ void _sendDeleteSignalRecursive(); /** * Adds increment to _total_hrefcount of object and its parents. */ void _updateTotalHRefCount(int increment); void _requireSVGVersion(unsigned major, unsigned minor) { _requireSVGVersion(Inkscape::Version(major, minor)); } /** * Lifts SVG version of all root objects to version. */ void _requireSVGVersion(Inkscape::Version version); sigc::signal _release_signal; sigc::signal _delete_signal; sigc::signal _position_changed_signal; sigc::signal _modified_signal; SPObject *_successor; CollectionPolicy _collection_policy; char *_label; mutable char *_default_label; // WARNING: // Methods below should not be used outside of the SP tree, // as they operate directly on the XML representation. // In future, they will be made protected. /** * Put object into object tree, under parent, and behind prev; * also update object's XML space. */ void attach(SPObject *object, SPObject *prev); /** * In list of object's children, move object behind prev. */ void reorder(SPObject* obj, SPObject *prev); /** * Remove object from parent's children, release and unref it. */ void detach(SPObject *object); /** * Return object's child whose node pointer equals repr. */ SPObject *get_child_by_repr(Inkscape::XML::Node *repr); void invoke_build(SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned); int getIntAttribute(char const *key, int def); unsigned getPosition(); char const * getAttribute(char const *name) const; void appendChild(Inkscape::XML::Node *child); void addChild(Inkscape::XML::Node *child,Inkscape::XML::Node *prev=nullptr); /** * Call virtual set() function of object. */ void setKeyValue(SPAttr key, char const *value); void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value); void setAttributeDouble(Inkscape::Util::const_char_ptr key, double value); void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value) { this->setAttribute(key.data(), (value.data() == nullptr || value.data()[0]=='\0') ? nullptr : value.data()); } /** * Read value of key attribute from XML node into object. */ void readAttr(char const *key); void readAttr(SPAttr keyid); char const *getTagName() const; void removeAttribute(char const *key); void setCSS(SPCSSAttr *css, char const *attr); void changeCSS(SPCSSAttr *css, char const *attr); bool storeAsDouble( char const *key, double *val ) const; private: // Private member functions used in the definitions of setTitle(), // setDesc(), title() and desc(). /** * 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 setTitleOrDesc(char const *value, char const *svg_tagname, bool 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. */ char * getTitleOrDesc(char const *svg_tagname) const; /** * Find the first child of this object with a given tag name, * and return it. Returns NULL if there is no matching child. */ SPObject * findFirstChild(char const *tagname) const; /** * Return the full textual content of an element (typically all the * content except the tags). * Must not be used on anything except elements. */ Glib::ustring textualContent() const; /* Real handlers of repr signals */ public: /** * Callback for attr_changed node event. */ static void repr_attr_changed(Inkscape::XML::Node *repr, char const *key, char const *oldval, char const *newval, bool is_interactive, void* data); /** * Callback for content_changed node event. */ static void repr_content_changed(Inkscape::XML::Node *repr, char const *oldcontent, char const *newcontent, void* data); /** * Callback for child_added node event. */ static void repr_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void* data); /** * Callback for remove_child node event. */ static void repr_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void *data); /** * Callback for order_changed node event. * * \todo fixme: */ static void repr_order_changed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, void* data); /** * Callback for name_changed node event */ static void repr_name_changed(Inkscape::XML::Node* repr, gchar const* oldname, gchar const* newname, void * data); friend class SPObjectImpl; protected: virtual void build(SPDocument* doc, Inkscape::XML::Node* repr); virtual void release(); virtual void child_added(Inkscape::XML::Node* child, Inkscape::XML::Node* ref); virtual void remove_child(Inkscape::XML::Node* child); virtual void order_changed(Inkscape::XML::Node* child, Inkscape::XML::Node* old_repr, Inkscape::XML::Node* new_repr); virtual void tag_name_changed(gchar const* oldname, gchar const* newname); virtual void set(SPAttr key, const char* value); virtual void update(SPCtx* ctx, unsigned int flags); virtual void modified(unsigned int flags); virtual Inkscape::XML::Node* write(Inkscape::XML::Document* doc, Inkscape::XML::Node* repr, unsigned int flags); typedef boost::intrusive::list_member_hook<> ListHook; ListHook _child_hook; public: typedef boost::intrusive::list< SPObject, boost::intrusive::member_hook< SPObject, ListHook, &SPObject::_child_hook >> ChildrenList; ChildrenList children; virtual void read_content(); void recursivePrintTree(unsigned level = 0); // For debugging static unsigned indent_level; void objectTrace( std::string const &, bool in=true, unsigned flags=0 ); }; std::ostream &operator<<(std::ostream &out, const SPObject &o); /** * 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 sp_object_compare_position(SPObject const *first, SPObject const *second); bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second); gchar * sp_object_get_unique_id(SPObject *object, gchar const *defid); #endif // SP_OBJECT_H_SEEN /* 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:fileencoding=utf-8:textwidth=99 :