The editor module classes form a platform independent library to use for 2D Vector Graphics within a document view context. It allows you to define a hierarchy of graphical objects, which can be displayed using a view. This view can be a window, a bitmap or some printer like device. The EditorModule classes are build upon the DocviewModule and the CanvasModule classes.
The first section is a list of one liners to quickly get an idea what EditorModule is all about. Next the global Design of the EditorModule library will be explained. At last, based on a list of features, you will get an idea what can be done with this library.
The EditorModule is build on top of the CanvasModule, but extends and uses many of the ingredients which are located in the CanvasModule. Like drawing tools and the drawings itself are located in the CanvasModule, the Editor Module mainly adds the a2dCanvasDocument which can contain a2dDrawing. Around this saving, loading, import, export classes, make it possible to fill a a2dCanvasDocument.
Features as One Liners
The next list of one liners, should give an idea what to expect in the wxArt2D library.
- canvas for displaying graphical objects
- zooming to any part of the world coordinates system
- graphical data stored independent from the display engines
- The same or different graphical data can be displayed on multiple view objects at the same time.
- modified canvas objects will be automatically updated in idle time, unless the user prevents it.
- only areas occupied by a modified object will be updated, and not the whole display.
- data can be saved and loaded as SVG and CVG (wxArt2D specific format)
- New readers and writers can easily be added
- advanged clipping strategy for quickly rendering specific parts of the graphical data
- pluggable tools for manipulating the data displayed, like draw, delete, drag, copy, edit
- toolcontroller stack based to allow zooming while drawing is in progress
- references to graphical objects, to display the same object at N places on a canvas
- reference counting system for graphical objects, stroke's, fill's and dynamic properties
- affine matrix transformations for each canvas object
- nested hierarchy of graphical objects
- rendering engine abstract and several implementations can be used at the same time
- Double buffering for quick refreshing and flicker free editing and drawing
- scrolling, with only partial updating when possible
- rendering (display and editing) on several canvas objects with same or different canvas documents and levels within a document.
- visible and invisible properties on canvas objects
- layers/levels of drawing, with drawing order that can be set independent of the layer order
- library (clipart) of objects possible, with reference to objects in the library
- fast draging using backup buffer
- flexible style objects, e.g. gradient fill styles
- quick fill and stroke setting when no change to previous setting, by using pointers and reference counting.
- multiple documents and views, simular to the wxWindows Docview classes
- optimized for minimum memory usage in base canvas object. Only when used a feature requires memory.
- mouse events for canvas objects via standard wxWindows event handling system.
- curve object, and groups of curves and markers on those curves
- eval of string functions, used in curve object
- world coordinate objects can be extended in pixels, for making pixel objects
- pixel and world coordinate pen width
- vector path object with line, arc, cubic and qaudratic bezier curve segments
- command language for automatic processing (in progress and hopefully python in the future)
- redo and undo in combination with tools and command processor
- printing of views
- bitmap or window rendering
- clippath can be set for each object
- double buffered rendering in combination with direct drawing to see progress
- communication between objects, by means of a special canvas event manager
How is it organized
The wxArt2D library is structured like a multiple document and view framework. A document manager has a list of documents. Each document has a list of views. A View is associated with a wxFrame or Subwindows within a frame. Frames and Sub-Windows are owned by parent windows and at the top by the wxApp class. WxWindows always deletes its windows automatically, so when a view is removed from a document, the associated window can be flagged for destroy. The destroy does not happen directly, instead when appropriate it is done by wxWindows. If a Frame with an associated view is closed, the view will be deleted and removed from the document. The figure wxArt2D storage show an application having 3 documents, document 1 has 3 views. View1 is using Frame1 to display itself. View 2 and 3 use Frame2 to display themselfs. Document 2 has 2 views, and they use Frame3 for the display. At last Document 3 has one view, displayed on the Parent Frame of the application.
Communication in the docview classes is done by sending events. A view sents a viewclose event to its display window and document, when it wants to close. When a a2dView sents events to a a2dDocumentViewWindow, the last will either handle the event or sent it up to the parent window, which is often a wxFrame. This way views inform a wxFrame of closing, and the frame can react by destroying itself, or by disconnecting the view.
New views and documents are/can be generated by the templates a2dViewTemplate and a2dDocumentTemplate. Those templates generate a view or document, and inform a a2dViewConnector of such an event. The a2dViewConnector will then be responsible for connecting the new views to the application its windows. The a2dViewConnector may first generate a new wxFrame plus a a2dDocumentViewWindow, and use that window to connect the new view. In an other application, it may use an existing a2dDocumentViewWindow, disconnect the connected view, and connect the new view to the window.
A view always has only one wxWindow pointer internal, which it uses to display itself on to. So if a wxFrame displays two views, this means that the wxFrame has two wxWindows as children, and those two windows are used by the a2dView's to display the view.
In wxArt2D each canvas document has an a2dDrawing which contains a2dCanvasObject's in a hierarchy. Each view can display a different part of the hierarchy. A view is independent from other views, and only changes when the document is changed or the view its viewport is modified.
WxArt2D can also be used in a more simple manner. The Document Manager is not used in that situation. The figure wxArt2D storage single canvas contains one a2dCanvas. The a2dCanvas which is derived from a2dDocumentViewWindow, and by itself is likely a child of a wxFrame. It owns one a2dCanvasView and a2dCanvasDocument. When destroying the window also the a2dDrawingPart and a2dCanvasDocument or deleted. The above is already enough to display graphic data from the document. And changes made to the document will be updated automatically. Next to this you may plug a a2dToolContr into the a2dDrawingPart. The a2dToolContr allows to push and pop tools on a stack. The tools are interactive, and in general used to change the views visible area (zoom), or they can add a2dCanvasObject's and manipulate the a2dCanvasObject's in the document. Here a2dCanvas its a2dCanvasDocument is containing two a2dCanvasObject's at the top, both contains two nested a2dCanvasObject's themselfs. Events coming in at the a2dCanvas window, are redirected to the a2dCanvasView first. The a2dCanvasView redirects the events to the toolcontroller, when it is available. If not handled in the tool controller, the event is futher processed in a2dCanvasView. a2dCanvasView does a hit test and redirects the event's to the a2dCanvasObject's that are hit by the mouse pointer. The tool controller will redirect the events to the first tool on the tool stack. The tool that is active can handle the event, and do a hit test on any a2dCanvasObject itself. The object hit will now recieve events directly from the tool. In case of drawing a new object, the tool will handle all mouse events, and based on those events draw a new object. When the drawing is finished, the object will be added to the document via the a2dCanvasCommandProcessor. This directly ensures being able to undo redo.
The figure wxArt2D Multiple document shows a more complicated setup. In a multiple document configuration a2dCanvasView can have a tool controller inserted in each a2dCanvasView. A tool controller has a stack of tools. One Tool is active in general. The active tool intercepts events coming from the a2dCanvas window via the wxDarwer and tool controller. Tools can be used to interactive draw new objects and/or to edit the existing objects. This can happen independent in several a2dCanvas windows at the same time. Even when it concerns the same document. All synchronization is taken care of.
a2dCanvasView derived classes are able to display a drawing contained within a tree as a hiearchy of drawable objects all derived from a2dCanvasObject. This is called a a2dCanvasDocument. A a2dCanvas uses a a2dCanvasView to display part of a a2dCanvasDocument. And its main purpose is to redirect events to and from a a2dCanvasView. a2dCanvas is able to scroll a a2dCanvasView. Scrolling means that the viewport of the a2dCanvasView is changed when scrollbar events are received by the a2dCanvas window. Hiearchy is created by adding children to a a2dCanvasObject or by using Special Reference objects. In principle every type of hierarchy is using references to a2dCanvasObject's. All canvas objects are reference counted, and removing the last reference to a a2dCanvasObject's will also delete the object. Only the way the objects are placed in the hiearchy of objects ( e.g children or subobjects of more advanced objects like end points of lines ), defines how they will be rendered. References make it possible to place/render a small drawing at several places on a view while only storing it once in memory. Internal a2dCanvasDocument uses a a2dCanvasView as a graphical context to render objects to the a2dCanvas window or any other type of device. The viewport set for the a2dCanvasView is defined in worldcoordinates and the mapping matrix takes care of converting a2dCanvasObject (relative) world coordinates to device coordinates.
The WxArt2D library at the bottom is based on a modifed version of the DocView wxWindow classes. 'Therefore there is a a2dDocument called a2dCanvasDocument and a a2dView called a2dCanvasView. Both a2dDocument and a2dView are derived wxEvtHandler, and can receive events. The a2dCanvasDocument always holds one a2dCanvasObject which is called the root object. All other a2dCanvasObject's in a document are nested children of this object. The a2dCanvasView class holds a pointer to a a2dDrawer2D class. This class is used to draw basic primitives on a device, while the parent class a2dCanvasView is doing all the update work of modified objects and redraw areas. The a2dDrawer2D is an abstract baseclass for the device specific drawers. Because a2dDrawer2D is abstract and contains mainly basic drawing routines, a2dDrawer2D's can be written for anything that one can possibly be used to drawn on. Being a wxWindow, wxBitmap, wxImage, Printer, as long as a a2dDrawer2D for that type of the device is available, it can be done. There can be several a2dCanvasDocument's open at the same time, and each can be displayed at several a2dCanvasView's too. This is done in such a way that quick updating of areas on all the views is still garanteed.
Events from the a2dCanvas class are first handled by its a2dCanvasView, a2dCanvasView redirect mouse events to its ShowObject, which is the top a2dCanvasObject to be shown on that a2dCanvasView. From there events go to nested a2dCanvasObjects. When the event is not handled at the lowest, at travels up to the parent a2dCanvasObjects.
The graphical objects with baseclass a2dCanvasObject are all stored in the class a2dCanvasDocument. The top a2dCanvasObject's in a tree of a2dCanvasObject's is called the root object, and is located in the a2dCanvasDocument. A a2dCanvasView is given a pointer to a a2dCanvasObject within a a2dCanvasDocument which needs to be displayed. This can be the a2dCanvasDocument its root object or any of its nested a2dCanvasObject children. The root object does contain as children a2dCanvasObject's in a recursive manner, this is the way to create hiearchy. Any level within the tree of a2dCanvasObject's a2dCanvasDocument (being a a2dCanvasObject with or without children ) can be displayed on a a2dCanvasView. At the same time a different a2dCanvasObject/level within the same document can be displayed on a different a2dCanvasView.
For quick redraws on each view, a boundingbox in world coordinates is stored inside every a2dCanvasObject. This boundingbox is used to clip the objects in the document to the area that is currently updated. The boudingboxes are stored in worldcoordinates and not pixels/device coordinates. The reason is that the objects itself are defined relative to its parent objects, for this a matrix is used. The matrix translates, scales, mirrors and rotates the object, relative to any parent object which contains a reference to this object. Since an object can have several parents at the same time, it does not have a predefined absolute position in world or device coordinates. Therefore the boudingbox of an object can only be stored relative to the parent objects. The boudingbox is in relative world coordinates instead of device, to make it easy to transform the boudingbox to an absolute position, inclusive rotation and scaling. The boudingbox includes the local matrix transformation that the object has. In order to clip an object to the arae that needs to be redrawn on a certain device, we need an absolute boundingbox for the object. To be able to calculate this absolute boudingbox, and also the absolute object coordinates, during the rendering the accumulated matrix is calculated. This matrix defines the position that the object to render will have on a drawing device, and each time the rendering goes deeper into hiearchy, this matrix is extended to include that level of hiearchy also. This matrix makes it possible to transform relative object coordinates to absolute world coordinates. Knowing the drawer that is being rendered, the calculated absolute object coordinates can be transformed to device coordinates in the end. In reality the accumulated matrix is directly combined inside the a2dDrawer2D to do this conversion from relative world coordinates into device coordinates in one matrix calculation. The pen width is not included in the boundingbox, but a world extend and a pixel extend are calculated for canvasobject that are added to the boundingbox when needed.
BoundingBoxes are calculated independently from the rendering stage of the document. They are not used only for clipping to the redraw area, but also to bring the document from one state into a new state after changing the object(s) document. When changing a a2dCanvasObject a flag is set which eventually triggers the redrawing of this object. All the areas which are occupied by the object on all views where it is displayed upon will be redrawn. The trick is that this will be done for the area occupied by the object in the state before the change, and after that for the area occupied by the object in the new state. The old area is available in the boundingbox of the object, just before recalculating it after a change to the object. Both the old and new area are reported to the a2dCanvasView's that display the object. The real redrawing/updating of those areas is done when all the areas have bin reported. This has the advantage that changes to many objects can be done before redrawing will take place. A a2dCanvasView has a list of areas which need to be redrawn, and if some changed objects overlap, the redraw areas are combined into one. After a document has reported all the changed areas to its a2dCanvasView's, it will start redrawing the areas. The redrawing starts at the drawer its top object, and traverses the document, meanwhile redrawing all objects in the area that is being updated. This is done for all update areas assembled in the a2dCanvasView. Before really redrawing an object, the rendering routine checks if the object needs to be rendered anyway, by first transforming only the boudingbox to absolute coordinates. If the resulting boudingbox intersects the area to be redrawn, real rendering of the object will take place. Every object that is completely outside the area that needs updating will not be drawn, the others will be clipped to the area to update. Graphical Clipping is not done inside the object itself, but is part of the a2dCanvasView, which is always used as the drawing context to do the basic drawing within a a2dCanvasObject. Still a rough boundingbox check is performed when rendering a document its tree of a2dCanvasObject's. The clip status ( inside, outside, intersecting ) of the parent object against a a2dCanvasView its display or update area is calculated, and is transferred to the child objects. Once a parent object is completely inside the update area , there is no need to check all its children.
a2dDrawer2D can be compared with a wxDc class, and for the wxDc based a2dDcDrawer it indeed uses the SetClippingRegion to prevent drawing outside the area to be redrawn. There is one more thing to add, an object can have a clipping path property set for it, which means that the object will be rendered only inside this clippingpath, the rest will not be vissible. This feature is also achieved inside the a2dDrawer2D. It is an additional clipping on top of the rectangular clipping for an, area needing an update. There is a clipping stack inside a2dDrawer2D that makes it possible to push and pop this object clipping path. The relative defined object clipping path is first transformed to absolute coordinates, and then pushed onto the stack. When the object has been drawn, it is poped from the stack again.
The mechanism within a a2dCanvasView for maintaining areas to be redrawn and to update those areas in idle time, is all rectangular based. The a2dCanvasView views understand how to update themselfs after a change to the data in a a2dCanvasDocument. The only thing the a2dCanvasView needs is the areas that where invalidated by the changes. It is possible that an invalidated area within the document is only visible on one view and not another, still damaged areas are reported to all views, but they are clipped to each specific view. Actually there are two mechanisms the a2dCanvasView uses, in order to know what needs to be redrawn. There is one list of areas to be redrawn in each a2dCanvasView, it can be filled directly by a program (e.g. editing tools). In second method a2dCanvasView will check a flag inside the a2dCanvasDocument that it needs to render. The flag within a a2dCanvasDocument is used to tell that it has so called pending objects. It is automatically set when an object changes. After a change each object needs to report this via the a2dCanvasDocument pointer that is a member of each a2dCanvasObject. Next to setting a flag inside the a2dCanvasDocument object, the changed a2dCanvasObject also sets a pending flag for itself. If the above flag is set, the a2dCanvasDocument's know that there are somewhere inside the document a2dCanvasObject's that did change and need to be rerendered. This is detected in idle time, and a2dCanvasDocument tells its a2dCanvasView's to search for the pending objects. Each a2dCanvasView will search those objects first, and add their current absolute boudingbox to the list of areas that need be redrawn. Next a2dCanvasDocument calculates the new boundingbox, for all the changed objects. And once more orders the a2dCanvasView's to add those new boundingboxes to the list of redraw areas. When all invalidated areas are added to the pending area list of each a2dCanvasView, the pending flags of all canvasobjects are reset. Each a2dCanvasView will now traverse the document for each invalidated area in its pendingarea list, and only re-render the objects overlapping each invalidated area. This normally happens in idle time or after an onpaint event. So in the end, the a2dCanvasView views will only redisplay those areas that are indeed visible on it. And the whole mechanism of pending objects prevents redrawing all views completely when only a small part has changed.
a2dCanvas takes care of scrolling and resizing, and handling mouse events, but most of the work is done by its a2dCanvasView, and often calls are redirected to the a2dCanvasView. When the user scrolls, the mapping for the a2dCanvasView is redefined, and scrollbars are adjusted. a2dCanvas isues the right update calls for a2dCanvasView to have efficient scrolling. The a2dCanvasView then calls the Render function of the a2dCanvasDocument, which will redraw what needs to be in the areas that where scrolled in the a2dCanvas window. So rendering is only done in those areas that really need to be rerendered. The same for a resize, it redefines the a2dCanvasView mapping and sets the new bitmap buffer for the a2dCanvasView based on the new size.
a2dCanvas uses a rectangular world coordinate system that is in Unit X,Y, wich means that vertexes can be meters, inch as well as bigs versus time. How large a2dCanvasObject's are on the screen is not coupled with the Units, all coordinates (independent of units) are stored as doubles or longs. When mapping the whole or part of a a2dCanvasDocument to the window the programmer can decide that 1 centimeter is indeed one centimeter on the screen also. When rendering a a2dCanvasObject its data is converted/presented into basic primitives defined in world coordinates, those primitives can be drawn with the a2dCanvasView that is active. Often the data in the canvasobject is directly related to the basic primitives e.g width and height of a rectangle object, but this is not a must. How the basic primitive coordinates are displayed on a certain a2dCanvasView view, is defined by the viewer its mapping. The a2dCanvasView views are designed for 2D (because of layers, actually 2.5D), and you can only draw in 2D on it. So if you put a 2D projection of the world map in a a2dCanvasDocument, you can tell a a2dCanvasView to only display Amsterdam. Even if the world object itself is really 3D, and inside the Mya2dCanvasWorldObject it is/can be stored as 3D coordinates, the drawing of Amsterdam will and needs to be 2D. Therefore in such a situation when rendering part of the 3D world object, this part is first projected to 2D within the object itself. Of course you can do this projection in such a manner that e.g. cube is eventually drawn as a 3D cube, but using 2D drawing methods. The projection task from 3D to 2D can be done via a matrix which transforms the 3D coordinates inside the Mya2dCanvasWorldObject to 2D coordinates. When can of course use any kind of data within a a2dCanvasObject, as long as the generated drawing is in 2D. This 2D drawing is placed relative to the parent a2dCanvasObject of the actual object to render. When rendering the object those coordinates will be transformed to an absolute position, which is defined by the accumulated matrix on the path leading from some top object in the a2dCanvasDocument to the a2dCanvasWorldObject to render. After this transformation to absolute 2D coordinates there is a second transformation inside a2dCanvasView, which defines the viewport. The viewport is best described as the eye position in 2D. This second transformation, transforms absolute coordinates to window coordinates. In reality a total transform matrix is set for a2dCanvasView to do both the transformation to absolute followed by translation into device in one matrix. For the moment i advice to use a square coordinate system, meaning the projection from world to device coordinates is the same in X and Y. Although every transform from object to view is 2 dimensional, the object itself is totaly free in generating the 2D canvas objects/primitives representing the object in some way. Those 2D objects will be drawn with the a2dCanvasView that is given as a parameter to the object rendering function via its a2dIterC pointer when it is rendered.
Some more examples. Asume you want to display a curve with a logarithmic scale, the conversion of the data from logarithmic to relative linear, needs to take place within the object, the rest is taken care of by the display rendering routines. The same situation will occur in the following cases: polar coordinates to rectangular, fractal objects generating 2d graphics on the basis of some formula. In general a a2dCanvasObject needs to project itself into relative 2D coordinates before the display engine wil do the rest.
The main a2dDrawer2D implementation, called a2dMemDcDrawer, has build in double buffering. Everything drawn to the a2dCanvasView view is drawn with a2dMemDcDrawer, which draws to a bitmap buffer. When drawing is ready, a2dCanvasView blits the buffer to the display window. This garanties flicker free drawing, and makes it possible to optimize redrawing in many ways. Another important use is redrawing parts that become visible when overlapping windows are (re)moved. This will lead to a paint event for the areas that will become visible, and all that a a2dCanvas/a2dCanvasView does to redisplay those parts, is blitting the right parts from the double buffer to the screen. Filling the double buffer is happening normally in idle (OnIdle) time. The double buffer in a2dMemDcDrawer makes it possible to implement dragging. Drawing and editing tools that make use of the buffer, work in such a manner that they work flicker free and fast. This strategy is called double buffering. An often used trick using the buffer is, freeze the buffer and draw and drag another object on the device which is a window. During the drag etc., the buffer is used to quickly redraw what was behind the object while dragging, and only the object that is dragged will be really redrawn.
A a2dCanvasDocument can be loaded and saved in several formats. As output and as input there are currently SVG and CVG (canvas vector graphics), the last one specially designed for the wxArt2D Library. The SVG loader, can load a subset of SVG. Adding other input and output formats is done via IOHandlers, and for all formats other than the CVG format the writing of a canvasobject is not integrated with the object itself as a member function. This makes it easy to plugin new handlers without changing the library itself. See Parsers for reading and Output formats.