summaryrefslogtreecommitdiffstats
path: root/doc/architecture.txt
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--doc/architecture.txt490
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>