diff options
Diffstat (limited to '')
-rw-r--r-- | doc/architecture.txt | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/doc/architecture.txt b/doc/architecture.txt new file mode 100644 index 0000000..961d960 --- /dev/null +++ b/doc/architecture.txt @@ -0,0 +1,490 @@ +Sodipodi internal architecture + +1. Overview + +The Sodipodi display and editing engine is built using the +"Model-View-Controller" (MVC) paradigm. Unlike "classic" MVC +programs, we have further split model into two distinct layers, +'backbone' and 'document'. This has proven to be extremely powerful +technique, giving us clear and fast implementation, functional +granularity and easy expandibility. + +1.1. Agnostic XML backbone + +The basis of the sodipodi document is its plain XML representation in +memory. This is a tree-shaped structure, in which each node is +represented by a lightweight typeless object (SPRepr). These objects +implement a minimal interface of both control (methods) and mutation +events (callbacks). We use the term 'agnostic' for describing that +part of model, to underline the typeless nature of SPRepr. More or +less, this is just an XML file representation in memory. + +1.2. Typed SVG document + +The most actively used part of the sodipodi document model is the SVG +object tree. This is constructed on top of the XML tree, and reacts to +all mutation events in the agnostic tree, thus always keeping its +internal state synchronized with the backbone. The opposite is not +true - the XML backbone is not aware of the SVG object tree, and thus +does not react to its modifications. If writeback to the backbone is +needed, it must be requested explicitly by the controller. The SVG +tree is constructed of SPObject subclasses - in general there is one +subclass for each SVG element type, plus abstract base classes. + +1.3. NRArena view + +NRarena is an abstract display engine that allows construction of +'display caches' from NRArenaItem subclasses. These are lightweight, +having only some basic object types, and used for most of the display +needs of Sodipodi. Both the editing window, and the bitmap export +code create special NRArena instances, and ask the SVG document to +show itself to the given NRArena. There is a ::show virtual method, +implemented by all visible object classes, that adds an NRArenaItem +node to the display tree. The completed display cache is used for fast +screen updates and stripe based bitmap exports. During the NRArena +lifetime SVG objects keep all of the display cache elements constantly +updated, thus ensuring the display is always up to date. + +1.4. Controllers + +Like the model suggests, controllers can be implemented acting on +different layers. Which one is best depends on the type of action +that the given controller performs. Usually very generic and +single-shot operating controllers act on the SPRepr layer, while those +providing visual feedback or tied to a certain object type act on the +SPObject layer. + + + +2. Detailed view + +2.1. SPRepr + +The most basic SVG (XML) document backbone is implemented as an +in-memory tree of SPRepr objects, each object corresponding to a +single node in the XML file. Currently there are only two types of +SPReprs - normal element nodes and text nodes. More types may be +added in the future, but the structure will probably always remain +much simpler (and faster) than DOM. + +SPRepr may have: +- attributes (keyword/value) pairs +- content (text) +- child nodes + +Attribute values are textual, and no checks are performed in that +layer to ensure document validity. Also, CSS style strings are +unparsed in that layer. The SPRepr tree is built during document +loading or creation. As it is textual and always synchronized with the +display, unfiltered saving involves just dumping it into a file. + +The basic API acting on SPRepr level is really spartan. + +SPRepr *sp_repr_new (const unsigned char *name) +SPRepr *sp_repr_new_text (const unsigned char *content) +SPRepr *sp_repr_ref (SPRepr *repr) +SPRepr *sp_repr_unref (SPRepr *repr) +SPRepr *sp_repr_duplicate (SPRepr *repr) + +int sp_repr_set_content (SPRepr *repr, const unsigned char *content) +int sp_repr_set_attr (SPRepr *repr, const unsigned char *key, const unsigned char *value) +int sp_repr_add_child (SPRepr *repr, SPRepr *child, SPRepr *ref) +int sp_repr_remove_child (SPRepr *repr, SPRepr *child) +int sp_repr_change_order (SPRepr *repr, SPRepr *child, SPRepr *ref) + +In addition there are some accessor methods and lot of convenience ones. + +Each SPRepr can have one or many event vectors associated with it. +Event vector is a block of callback pointers for different kind of +mutation events. + +void sp_repr_add_listener (SPRepr *repr, const SPReprEventVector *vector, void *data) +void sp_repr_remove_listener_by_data (SPRepr *repr, void *data) + +struct _SPReprEventVector { + void (* destroy) (SPRepr *repr, gpointer data); + gboolean (* add_child) (SPRepr *repr, SPRepr *child, SPRepr *ref, gpointer data); + void (* child_added) (SPRepr *repr, SPRepr *child, SPRepr *ref, gpointer data); + gboolean (* remove_child) (SPRepr *repr, SPRepr *child, SPRepr *ref, gpointer data); + void (* child_removed) (SPRepr *repr, SPRepr *child, SPRepr *ref, gpointer data); + gboolean (* change_attr) (SPRepr *repr, const guchar *key, const guchar *oldval, const guchar *newval, gpointer data); + void (* attr_changed) (SPRepr *repr, const guchar *key, const guchar *oldval, const guchar *newval, gpointer data); + gboolean (* change_content) (SPRepr *repr, const guchar *oldcontent, const guchar *newcontent, gpointer data); + void (* content_changed) (SPRepr *repr, const guchar *oldcontent, const guchar *newcontent, gpointer data); + gboolean (* change_order) (SPRepr *repr, SPRepr *child, SPRepr *oldref, SPRepr *newref, gpointer data); + void (* order_changed) (SPRepr *repr, SPRepr *child, SPRepr *oldref, SPRepr *newref, gpointer data); +} + +All events, except destroys (which are unconditional), have pre- and +post- event callbacks. The pre-event callback's return value is used to +signal whether the given modification is allowed. If it is FALSE the +operation will be cancelled and the invoking method will also return +FALSE. Using callbacks in block is much more convenient than adding +them one-by-one, as the listening code usually wants to install several +handlers at once, and the same set of handlers for many different +nodes. NULL pointers are allowed in event vector. + +Although the most important functionality of the SPRepr tree is to +serve as a document backbone, it has other functions besides +that. SPReprs are also used to store preferences, the copy buffer and +the undo stack. + + + +2.2. 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. + +SPObjects are implemented using the Gtk object system (GObjects in gtk +2 version), which provides an extremely powerful and flexible OO +framework in pure C. + +SPObject class hierarchy + +SPObject ABSTRACT + SPObjectGroup ABSTRACT + SPNamedView <sodipodi:namedview> + SPClipPath <clipPath> + SPGuide <sodipodi:guide> + SPPaintServer ABSTRACT + SPGradient ABSTRACT + SPLinearGradient <linearGradient> + SPRadialGradient <radialGradient> + SPPattern <pattern> + SPDefs <defs> + SPItem ABSTRACT + SPGroup <g> + SPRoot <svg> + SPAnchor <a> + SPImage <image> + SPPath ABSTARCT + SPShape <path> + SPLine <line> + SPPolyLine <polyline> + SPPolygon <polygon> + SPStar <sodipodi:star> + SPRect <rect> + SPSpiral <sodipodi:spiral> + SPGenericEllipse ABSTRACT + SPCircle <circle> + SPEllipse <ellipse> + SPArc <sodipodi:arc> + SPChars ABSTRACT + SPString TEXT NODE + SPDefs <defs> + SPText <text> + SPTSpan <tspan> + +SPObject internals + +struct _SPObject { + GtkObject object; + unsigned int hrefcount; + SPDocument *document; + SPObject *parent; + SPObject *next; + SPRepr *repr; + unsigned char *id; + SPStyle *style; + const unsigned char *title; + const unsigned char *description; +}; + +The basic refcounting is handled by the parent class +(GtkObject). Hrefcount is used for weak references, for example, to +determine whether any graphical element references a certain gradient +node. The parent and next fields are used to establish the tree +structure. Id is copy of the SPRepr 'id' attribute for normal nodes, +and is used as a unique index of all objects in the given document. + +Virtual methods + +/******** Disclaimer *******/ +This will change a lot in the future + +void ::build (SPObject *object, SPDocument *document, SPRepr *repr) + +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). + +void ::release (SPObject *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. + +void ::child_added (SPObject *object, SPRepr *child, SPRepr *ref) +void ::remove_child (SPObject *object, SPRepr *child) +void ::order_changed (SPObject *object, SPRepr *repr, SPRepr *oldref, SPRepr *newref) + +These are invoked whenever the given mutation event happens in the XML +tree. ::remove_child is invoked BEFORE removal from the XML tree +happens, so grouping objects can safely release the child data. The +other two will be invoked AFTER the actual XML tree mutation. Only +grouping objects have to implement these. + +void ::read_attr (SPObject *object, const unsigned char *key) + +Signals object that the XML attribute is changed. The frontend checks +for 'id' attribute; implementations have to read the actual attribute +value and update the internal state. + +void ::read_content (SPObject *object) + +Signals object that the XML node content has changed. Only meaningful for +SPString implementing XML TEXT node. + +void ::modified (SPObject *object, unsigned int flags) + +Virtual method AND signal implementing asynchronous state change +notification. Whenever the object internal state changes, it requests +that ::modified will be scheduled from the idle loop. Flags are given +as hints as to what exactly changes. Read the relevant section for +more information. + +SPRepr ::write (SPObject *object, SPRepr *repr, unsigned int flags) + +Requests SPObject internal state to be written back to the SPRepr. If +the SP_OBJECT_WRITE_BUILD flag is set, SPRepr is created, if necessary. +This is used at various places, most notably to generate a plain SVG +document, and to complete certain GUI operations. + + + +2.3. SPItem + +SPItem is an abstract base class for all graphic (visible) SVG nodes. It +is a subclass of SPObject, with great deal of specific functionality. + +SPItem internals + +struct _SPItem { + SPObject object; + unsigned int sensitive : 1; + unsigned int stop_paint: 1; + double affine[6]; + SPItemView *display; + SPClipPath *clip; +}; + +Affine is a 3x2 matrix describing transformation from the item to the +parent local coordinate systems. Each display in linked list has a link +to a single NRArenaItem that implements actual renderable image of +the item. + +Virtual methods + +/******** Disclaimer *******/ +This will change a lot in the future +Only the most important are listed + +void ::bbox (SPItem *item, ArtDRect *bbox, const double *transform) + +Calculates item's logical bounding box. The logical bbox does not +take into account the stroke width, nor certain other visual +properties. Transformation is a 3x2 matrix describing coordinate +transform from the item's local coordinate system to the coordinate +system of the bounding box. + +void ::print (SPItem *item, SPPrintContext *ctx) + +Prints the item's visual representation, using the internal printing +frontend. In the future this may be turned into a more generic +exporting method. + +char ::description (SPItem *item) + +Gives a short description of the item suitable for use in a statusbar, +etc. + +NRArenaItem ::show (SPItem *item, NRArena *arena) + +Creates an NRArena display cache representation of the item. The +frontend places the generated item into a hierarchy; implementations +have to build a correct NRArenaItem and keep it up to date. + +void (* hide) (SPitem *item, NRArena *arena) + +The opposite of ::show. + +void ::write_transform (SPItem *item, SPRepr *repr, double *transform) + +Tries to remove the extra transformation by modifying other aspects of +the item representation. For example, by changing the rectangle width +and height, the scaling component of the transformation can be +dropped. This is used to make the SVG file more human-readable. + +void ::menu (SPItem *item, SPDesktop *desktop, GtkMenu *menu) + +Appends item specific lines into the menu. This is used to generate +the context menu, and will probably move into a separate module in +the future. + + + +2.4 SPDocument + +SPDocument serves as the container of both model trees (agnostic XML +and typed object tree), and implements all of the document-level +functionality used by the program. + +SPDocument implements undo and redo stacks and an id-based object +dictionary. Thanks to unique id attributes, the latter can be used to +map from the XML tree back to the object tree. Documents are +themselves registered by the main program metaobject 'Sodipodi', which +does elementary bookkeeping. + +SPDocument performs the basic operations needed for asynchronous +update notification (SPObject ::modified virtual method), and implements +the 'modified' signal, as well. + +Many document level operations, like load, save, print, export and so on, +use SPDocument as their basic datatype. + +2.4.1. Undo and Redo implementation + +Using the split document model gives sodipodi a very simple and clean +undo implementation. Whenever mutation occurs in the XML tree, +SPObject invokes one of the five corresponding handlers of its +container document. This writes down a generic description of the +given action, and appends it to the recent action list, kept by the +document. There will be as many action records as there are mutation +events, which are all kept and processed together in the undo +stack. Two methods exist to indicate that the given action is completed: + +void sp_document_done (SPDocument *document) +void sp_document_maybe_done (SPDocument *document, const unsigned char *key) + +Both move the recent action list into the undo stack and clear the +list afterwards. While the first method does an unconditional push, +the second one first checks the key of the most recent stack entry. If +the keys are identical, the current action list is appended to the +existing stack entry, instead of pushing it onto its own. This +behaviour can be used to collect multi-step actions (like winding the +Gtk spinbutton) from the UI into a single undoable step. + +For controls implemented by Sodipodi itself, implementing undo as a +single step is usually done in a more efficient way. Most controls have +the abstract model of grab, drag, release, and change user +action. During the grab phase, all modifications are done to the +SPObject directly - i.e. they do not change XML tree, and thus do not +generate undo actions either. Only at the release phase (normally +associated with releasing the mousebutton), changes are written back +to the XML tree, thus generating only a single set of undo actions. + + +2.5. SPView and SPviewWidget + +SPView is an abstract base class of all UI document views. This +includes both the editing window and the SVG preview, but does not +include the non-UI RGBA buffer-based NRArenas nor the XML editor or +similar views. The SPView base class has very little functionality of +its own. + +SPViewWidget is a GUI widget that contain a single SPView. It is also +an abstract base class with little functionality of its own. + +2.6. SPDesktop + +SPDesktop is a subclass of SPView, implementing an editable document +canvas. It is extensively used by many UI controls that need certain +visual representations of their own. + +SPDesktop provides a certain set of SPCanvasItems, serving as GUI +layers of different control objects. The one containing the whole +document is the drawing layer. In addition to it, there are grid, +guide, sketch and control layers. The sketch layer is used for +temporary drawing objects, before the real objects in document are +created. The control layer contains editing knots, rubberband and +similar non-document UI objects. + +Each SPDesktop is associated with a SPNamedView node of the document +tree. Currently, all desktops are created from a single main named +view, but in the future there may be support for different ones. +SPNamedView serves as an in-document container for desktop-related +data, like grid and guideline placement, snapping options and so on. + +Associated with each SPDesktop are the two most important editing +related objects - SPSelection and SPEventContext. + +Sodipodi keeps track of the active desktop and invokes notification +signals whenever it changes. UI elements can use these to update their +display to the selection of the currently active editing window. + +2.7. SPSelection + +This is a per-desktop object that keeps the list of selected objects +at the given desktop. Both SPItem and SPRepr lists can be retrieved +from the selection. Many actions operate on the selection, so it is +widely used throughout the Sodipodi code. + +SPSelection also implements its own asynchronous notification signals, +that UI elements can listen to. + +2.8. SPEventContext + +SPEventContext is an abstract base class of all tools. As the name +indicates, event context implementations process UI events (mouse +movements and keypresses) and take actions (like creating or modifying +objects). There is one event context implementation for each tool, +plus few abstract base classes. Writing a new tool involves +subclassing SPEventContext. + + + +3. Some thoughts + +3.1. Why do we need a two-level model tree? + +The need for a typed object tree is obvious if we want to utilize OO +programming - which we certainly want to do. Although implemented in pure C, +Sodipodi uses the gtk (glib in future versions) type and object system, +which gives us an extremely powerful set of OO functionality. As SVG is +designed with inheritance in mind, using object subclassing to represent +it is perfectly the right thing to do. + +But there are also areas where typed object structure would make +things more complex. For example, to implement the copy buffer we had +to save the full state of copied objects. While this could be done +with the separate virtual method of SPObject, we can use a much easier +way and create the duplicate corresponding SPRepr. As our document +model already has to implement generation of full object +representation for SPRepr tree of nodes, generation of new objects +during paste happens automatically when the given SPRepr is inserted +into XML tree. The agnostic xml tree is also used for undo stack, as +described earlier. + +The main benefit comes from the extreme simplicity of the XML tree +manipulation API. All operations can be done, using only around 10 +methods, which makes code much more robust, and is perfect for +implementing compatibility sensitive things, like a plugin API. + +The XML tree also makes implementing two SVG features - cloning and +animations - much easier by providing an invariant backbone. + + + +22. Novemebr 2002 +Lauris Kaplinski +<lauris@kaplinski.com> |