Revision 1 as of 2016-04-30 08:50:22
|Deletions are marked like this.||Additions are marked like this.|
|Line 1:||Line 1:|
|= Canvas module =||= Editor module =|
- Editor module
- Command processor
- graphical data stored in a2dCanvasDocument
- Events and Hittest on objects
- Rendering a document
- Automatic Update of Drawing
- Grouping objects
- Affine Matrix
- Layers for drawing
- (poly)Line End and Begin points
- Stroke andFill properties
- The Clipping Property
- the name property
- a2dCanvasObject mask
- searching and collecting
- Graphs and Diagrams
Other topics on this module:
<<ListPages(search_term=u'CanvasModule', list_type=bullet_list, revers=True)>>
The canvas module classes form a platform independent library to use for 2D Vector Graphics. 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. On the top wxArt2D classes are build upon the DocviewModule classes.
The first section is a list of one liners to quickly get an idea what CanvasModule is all about. Next the global Design of the CanvasModule library will be explained. At last based on a list of features, you will get an idea what can be done with this library.
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 structered 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 to. In 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 Document 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 a2dCanvas contains one a2dCanvas. The a2dCanvas which is derived from a2dDocumentViewWindow, and by itself is part of a wxFrame. It owns one a2dCanvasView and a2dCanvasDocument. When destroying the window also the a2dCanvasView 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 a2dCanvasView. 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.
The WxArt2D library uses Tools to drag, move, rotate, scale, select, edit, group, ungroup a2dCanvasObjects that are displayed on a a2dCanvas. A set of tools is maintained by a toolcontroller, which has a list of tools that can be made active one at the time, or if needed more tools can be activated at the same time. A toolcontroller can be plugged into a a2dCanvasView object when you need it. The a2dCanvasView is the drawing for all types of canvas objects like a2dCanvas and a2dCanvasSim. A special set of tools has been written to manipulate objects on the canvas. They all work flicker free, and allow zooming while in action, which makes extremely accurate drawing possible. The editing tools (to edit objects interactive), work with a copy of the a2dCanvasObject to edit. This copy of the a2dCanvasObject is set into editing mode by the editing tool, which results in displaying handles for dragging. The editing tool redirects the mouse events ( and others ) to the editing copy object. Each editing handle in the copy object has a predefined action which modifies the copy object, and at predefined moments will modify the original object via commands. Because we want to be able to Undo and Redo changes, commands will be sent to the a2dCanvasCommandProcessor from within the copy object that is being edited, from there the commands will modify the original object. Every fundamental change ( e.g. move x,y or fill and stroke), is sent as a a2dCanvasCommand to the a2dCanvasCommandProcessor of the a2dCanvasDocument. This is how the orginal object stays in sync with the copy that is being edited. The Number of commands sent, define what exactly can be undone. So for a drag only the start and end of the drag will result in a command.
Any a2dCanvasObject derived object is editable. The default editing mode only modifies the transform the matrix of the a2dCanvasObject. The derived object itself is free to implement any other type of editing functionality it needs. When a2dCanvasObject::EditStart() is called on an object, the object is prepared for editing by (generating and) displaying the editing handles. Event tables intercept the events for the object, and when in editing mode, the mouse events that hit and drag the editing handles will result in changing the object itself. All editing is concentrated and defined within the a2dCanvasObject itself. Editing ends when EditEnd() is called, which removes or hides the editing handles and disables editing mode. When an object is in editing mode a flag is set to indicate this. The way a a2dCanvasObject is Rendered can be changed when in editing mode by changing the rendering behaviour when the editing flag is set. Drawing handles etc., are stored inside the a2dCanvasObject itself. This can be as real C++ members, or by using a2dCanvasPropertyBase properties and childobjects like a2dHandle. The last is the best is you want to optimize memory usage, since they can be created dynamically at the start of the editing and removed at the end. So all the extra data needed to edit an object is not stored in the a2dCanvasDocument all the time, only temporarely when editing the object. The above requires a certain strategy for editing an object, which is under supervision of the editing tool. Still you are free to develop tools and canvasobject's that take a different approach. For a medium size drawing, it is propably easier to store all editing features inside the object itself. And if the data stored in the object is big anyway, a few extra bytes do not hurt that much.
The Command processor processes a2dCanvasCommand's and saves on the undo stack data to reverse the change from the old state into the new state of the canvasobject in some manner. Not only editing result in commands, also drawing new object are added to the document via commands. All modifications to a document can therefore be Undone.
See Editing Objects for a picture of editing in progress. The lowest only is editing the matrix of the object, offering handles to scale, rotate and skew the matrix of the object. This manner of editing will be choosen automatically when no specific object editing is implemented.
The canvas tools use the a2dCanvasCommandProcessor to modify objects in a a2dCanvasDocument. After an interactive tool has done a certain operation, it sends the change as a command to the command processor. This makes it possible to implement undo and redo of the tool actions. The a2dCanvasCommandProcessor is local to the document, and only maintains the changes to its document. A second global a2dCentralCanvasCommandProcessor is used to load processing scripts/files, containing commands that can modify the data in the whole application. It can execute command like strings, to modify the contents of a2dCanvasDocument's in a non interactive manner, and also has commands to open and close files etc. The graphical user interface of the application can use this commandprocessor to open and close files. The commandprocessor can record the commands, and store them into a file as strings, and that's the way to create macro files. The ultimate goal is to use wxPython scripts for that, and once the wxArt2D library is wrapped into wxPython, this will be possible. This might lead to a second form of a2dCanvasCommandprocessor in the future, which is an integrated (wx)python interpreter. Imagine wxWindows C++ code loading wxPython scripts that modify a2dCanvasDocument's and create new views for the document. The main GUI interface will still be in wxWindows c++ code, but the wxPython interpreter will be able to add extra dialogs and such to extend a program based on the wxArt2D Library. For the moment the central command processor will contain only very basic string commands that can be executed. The a2dCentralCanvasCommandProcessor does also have member functions to directly add basic objects to a document. Functions like SetStroke(..) SetFill() AddRectangle( w, h .... ) , make it possible to set the current fill and stroke once for the command processor, and use it for all subsequent objects that are added to the document. When setting things like Fill and Stroke, special pending events are sent to a central eventmanager, telling all registrated tools or dialogs, that something has changed resulting in updating themselfs ( as soon as the program becomes idle).
The part of the world coordinate system that is shown on a a2dCanvasView its device ( e.g a2dCanvas window) can be set to any part of the world coordinate system. Being (MINDOUBLE to MAXDOUBLE in both X and Y) The conversion from worldcoordinates to device coordinates is called mapping. The mapping allows you to map any part/viewport of the worldcoordinate system to a window or bitmap. The size of the canvas window is taken into account to calculate the right mapping. In order to have the same amount of user units per pixel in both X and Y, a given area in world coordinates may be adjusted to achieve this. This makes sure that at least the given area in world coordinates will be shown. Zooming into a drawing is just a matter of setting a new mapping matrix for the a2dCanvasView used for a a2dCanvas, resulting in a new area shown on a a2dCanvas. See Zooming for a picture. Scrolling means also modifying the mapping, but it only shifts, and the width and height of the mapping stay the same. This is why scrolling small amounts can be optimized, only the parts that were not visible are redrawn, the rest is shifted.
graphical data stored in a2dCanvasDocument
a2dCanvas is able to display data that is stored in a a2dCanvasDocument object. Internally it uses a a2dView calleda2dCanvasView to do the rendering. a2dCanvasDocument in general is not depending on the displaying device, it can also exist without a a2dCanvasView/a2dCanvas. The document uses an abstract a2dDrawer2D to draw itself to that a2dCanvasView view. The a2dDrawer2D may decide to directly draw on a device or first store the drawing internally and when everything is drawn, draw its internally stored drawing to the device. The last is what happens in case of double buffering. The a2dCanvasView takes care of mapping part of the document to the device and all update mechanisms or organized in here. But each a2dCanvasView has a a2dDrawer2D to do the actual drawing. The a2dCanvasDocument does use the a2dDrawer2D to draw itself. To optimize drawing speed, only changed objects in the document will be redrawn. A boundingbox will be calculated to clip the canvas objects in the document against the a2dCanvasView. The boundingbox is defined in world coordinates, but two extra extends can be set within the object. This makes it possible to use pixel strokes within objects, and to some extend completely pixel defined objects ( e.g. editing handles ). The stroke size of the stroke used within the object is not part of the boundingbox, it is added when the boundingbox is asked for. The reason is that object have there boundingbox defined relative to it parent objects, to calculate the absolute boudingboxes at the top level in a wxDrawer, it required to transform the relative boundingbox first to the absolute position, and then to add the stroke size to it.
The a2dCanvasDocument contains one root a2dCanvasObject. The root object has as children other a2dCanvasObjects and they can be nested. Objects can be defined in world or pixels coordinates, or a mix of that. When rendering starts, the specific a2dCanvasView is set to the document iterations context. This iteration context class a2dIterC, is used when traversing the document hierarchy. This iteration context is given as a parameter to the a2dCanvasDocumentrendering functions. The translation to device coordinates is done within the a2dCanvasView class. Each a2dCanvasObject must set the Absolute matrix to a2dDrawer2D in order to draw itself such that it is automatically converted from relative world coordinates to absolute world coordinates. This matrix is combined with the a2dDrawer2D its mapping matrix for conversion to device coordinates directly. The same combined matrix will be used to calculate device coordinates for the subsequent calls to the a2dDrawer2Dits basic primitive drawing functions. Those functions are used by the object to display itself in some manner on the a2dCanvasView, which is a a2dView. The a2dCanvasView will eventually or directly display itself on the device, which is in general a a2dCanvas. Of course the a2dCanvasObject has a limited set of drawing primitives to display itself, being everything that is supported by a2dDrawer2D. But since amoungst them is drawing Images and Bitmaps, for complex things one can render the object to a bitmap in any way that is needed. After that, the bitmap can be drawn with a2dDrawer2D to the device.
Events and Hittest on objects
Mouse and Character Events are intercepted in a a2dCanvas window and redirected to a2dCanvasView. a2dCanvasView redirects the events to the a2dCanvasObject which is shown on that a2dCanvasView. Often this is the root object of the a2dCanvasDocument. The a2dCanvasObject will redirect the event to its children. In the end the a2dCanvasObject which has the mouse pointer on it, will receive the events. Still other object are tested also, since special events are generated by the a2dCanvasObject itself (e.g leaving and entering objects). If the original event from a2dCanvasView is a mouse event, a2dCanvasObject will add extra info, and sent it as a a2dCanvasObjectMouseEvent to itself. Now a derived a2dCanvasObject can add static event tables, and that is how events are intercepted in derived objects. If a child object does not handle the event eventually, its parent object will get the event. This continues until the event arrives at the a2dCanvasView again. A a2dCanvasObject can also Capture all events which are sent to the a2dCanvas window. Intercepting Events is via a Static Event Table, and in the same manner as intercepting events on a wxWindow. Some a2dCanvasObjects, generate new events based on the basic a2dCanvasObject events. For instance a2dHandle will generate the a2dHandleMouseEvent event and sent it directly to its parent a2dCanvasObject. The parent object does intercept this type of event to implement editing. When dragging a a2dHandle, this will effect the parent object. See Events for more.
Rendering a document
The rendering of a document its contents is not limited to a window. a2dCanvasView can be used to display a a2dCanvasDocument drawing on a wxWindow derived class. But internal it uses a2dDrawer2D to do the drawing. A a2dDrawer2D can draw also to a bitmap or printer etc. a2dCanvasView just uses a WxDrawer2D to display a bitmap in a window. A special a2dCanvasObject's called a2dRenderImage is available to be able to use a wxDrawer view with its own document as part of another a2dCanvasDocument. As such it is a a2dCanvasObject inside a higher level a2dCanvasDocument. It all depends on the implementation of a a2dDrawer2D how and where the a2dCanvasDocument is displayed. For the a2dCanvasDocument that contains the data it makes no difference at all. The only limitations is that the render member function of a a2dCanvasObject can only use the drawing capabilities that or offered by the a2dDrawer2D base class. The a2dDrawer2D implements the simple drawing of primitives, its parent a2dCanvasView takes care of advanged update schemas. It controls a a2dCanvasDocument asking it to re-render certain areas that need an update. A flag is set in a a2dCanvasDocument when it is modified. The a2dCanvasDocument checks this in idle time and will start traversing the document to find the objects/areas that did change. All changed areas will be reported to all wxDrawer's used by the a2dCanvasDocument. This update sequence takes into account the area occupied by the object in its previous and current state. When this is ready the a2dCanvasView's will start redrawing the modified areas at the next idle event in a2dCanvasView. So the user does not have to take care of anything after modifying or moving objects etc. Another advantage is that many changes to a a2dCanvasDocument can be made, before real redrawing takes place only once. Since redrawing areas are combined in a2dCanvasView, this minimizes the number of redraws a lot.
Rendering of a a2dCanvasObject's and its children, is all aranged through recursive calls to a2dCanvasObject::Render. This function controls how child objects will be rendered, and what effect certain properties do have on the object to render. When an object has private style properties they will be set here as the current style for the active a2dDrawer2D, else the layer of the object will be used to set the style. The function also calls a2dCanvasObject::DoRender, which is virtual and should be overridden to define how a specific object should be rendered. So in case of a circle object, the base class defines color, fillpatern, outline etc. but the DoRender function really draws the circle using the active a2dDrawer2D. You may decide to completely redefine how an Object and its children will be rendered, since the rendering functions or virtual. For instance, assume you want to make a text block object which has only one type of children, being the lines of text in the text block object. In such a case when the text block contains 1000 lines of text, one soon needs an optimized rendering strategy, in order to reduce the number of lines to redraw to a minimum when editing just one line. Therefore in such cases you will override the default a2dCanvasObject rendering.
Automatic Update of Drawing
When a certain a2dCanvasObject changes, it sets itselfs pending, and informs its a2dCanvasDocument that it needs an update, by setting a flag in there also. In idle time the a2dCanvasDocument flag is checked, and if TRUE all a2dCanvasObjects are checked and all pending a2dCanvasObjects issue pending update areas to the a2dCanvasView instances that the a2dCanvasDocument uses to display itself. Those update areas are based on the previous and the current area occupied by the a2dCanvasObject (e.g. old and new boundingbox of object made absolute and converted to device coordinates). The a2dCanvasView's assemble those so called update areas, and as soon as the program is idle they will start re-rendering those areas. When needed, the rendering of pending objects and/or update areas can be forced also. Rendering starts in a2dCanvasView with the so called ShowObject() and continues by traversing its children whcihc are all stored in the a2dCanvasDocument . The iteration context a2dIterC class is used to hold the current a2dCanvasView. This last has the a2dDrawer2D, which a2dCanvasObject uses to draw itself on the active drawer. The above in the end results in all the a2dCanvasView's having up to date buffers, containing the wanted viewports from the a2dCanvasDocument. In case of a a2dCanvas, the contents of the a2dDrawer2D buffer is displayed on screen. In the a2dCanvasView::OnIdle and in a2dCanvasDocument::OnIdle the a2dCanvasDocument and its a2dCanvasView's are checked for available updated, and after redrawing them to the buffer they will be blitted to the screen.
When changing many objects, this results in many areas needing an update. Each area is added to the updatelist, which is maintained within each a2dCanvasView. Inside a2dCanvasView there is a mechanism to detect overlapping areas. This is based on a tilling principle. The buffer is divided in a grid of rectangular areas, and for each rectangle it is recorded if it need to be redrawn or not. So worst case one only needs to redraw all rectangles. If several update areas overlap the same rectangle, that rectangle will only be redrawn once, and not again and again for each update area. The real mechanism is a bit more complex, for more information on that see a2dTiles All this is needed to only (re)render the minimum amount of areas.
The update process in a document is recursive, it calculates new boudingboxes for all objects which did change.
See for more DrawingAndUpdating.
All the data in a a2dCanvasDocument is organized in groups of a2dCanvasObject's, Every a2dCanvasObject can have a list of nested a2dCanvasObject's , which are called children. a2dCanvasDocument itself always has one a2dCanvasObject, which is called the root object. The children and nested children of the root object are displayed when a whole a2dCanvasDocument is rendered to a a2dCanvasView. Any nested child can be used by a2dCanvasView to start rendering only part of the document. Children are added to the a2dCanvasDocument to fill the document. But since the children itself can have children also etc., in the end this results in a tree like structure. All a2dCanvasObject's can be reached by recursive iterating over the document its children. A child of one a2dCanvasObject can at the same time be a child of other a2dCanvasObject's, this is a key feature of the wxArt2D library. This is not only the case for child objects, also other references can exist (e.g a2dCanvasObject's having members that point to other a2dCanvasObject's or storing a2dCanvasObject's on the Undo stack). To implement the above a2dCanvasObject is a reference counted object. Many pointers can point to the object that is reference counted, and the tell the object that the Own() it. The "pointing object" tells that the object pointed is to be Released when it no longer needs it. This is not the same as a real delete, and therefore is called Release. If all pointers pointing to a a2dCanvasObject or gone ( reference count reaches zero), the object will automatically be deleted for real. As consequence a a2dCanvasObject has not just one parent, and the document structure is not a strict tree, its branches touch eachother making it more a graph structure.
By default an object renders itself and its children, for derived object this can be extended to also render references to other objects which were stored as members of that object and are not part of the default child list. A Line with endpoints being references to other canvas objects is a good example of that. The difference is that children are always placed relative to (0,0) of the a2dCanvasObject with its matrix already applied, while in case of endpoint's on a line, the end objects are placed relative to both endpoints of the line.
Each a2dCanvasObject derived class always has a matrix to position itself relative to its parent objects. Where the parents can be one of the following:
a a2dCanvasObject to which it was added as a child
- a a2dCanvasObjectReference
any other object containing pointers/references to the object.
The matrix defines the position, rotation, scaling etc. relative to all parent objects. It is not adviced to place the same object twice in the same parenta2dCanvasObject as a child, since it will overlap itself (one matrix). See a2dCanvasObject and a2dCanvasDocument storage.
Position, rotation and scaling etc. for each a2dCanvasObject is set by an affine matrix. The a2dAffineMatrix is used to store translation, scaling, rotation, skew and mirroring in one simple matrix. A nice feature of this type of matrix is, that the can be multiplied with the matrix of the parent objects to find a total matrix which defines the absolute end position of a nested a2dCanvasObject. This matrix is always relative to the parent(s). To find the absolute position an object has, seen from the a2dCanvasDocument root group or any other parent object, all the matrixes of a2dCanvasObject's and a2dCanvasObjectReference's are multiplied recursive when rendering a document starting at some parent object. The resulting matrix is what is called the accumulated matrix. The a2dCanvasObject its own matrix also gets multiplied with the foregoing matrixes of parent objects. The resulting matrix gives the absolute position, rotation etc.. This matrix is set by the object into the active a2dDrawer2D, followed by rendering itself with that a2dDrawer2D. WxDrawer2D allows to draw basic primitives, and in combination with the above matrix, transforms them to the absolute position before drawing the primitive. So if you know how to draw the object in local relative coordinates (without any matrix stuff, simply relative to 0,0 ), then a2dDrawer2D will take care of the rest. All objects must be able to render itself making use of a a2dDrawer2D. The Basic a2dCanvasObject Rendering Function takes care of first setting the absolute transform matrix for the drawer. Having a matrix, can result in circles and ellipses which are rotated and scalled in X and Y different, in fact inside a2dDrawer2D it is converted to a polygon in order to be able to handle this. How a circle inside a a2dCanvasObject is drawn, depends on the matrix it has and also on the matrixes it parents have. In case the scaling in X and Y is the same and there is no rotation, you will get a perfect circle shape. In all other cases you normally get a rotated ellipse drawn to the canvas.
The affine matrix of the object can be changed, either interactive with tools or straight from your program. This will result in the object being updated automatically, which is the case in general when changing an object. See a2dCanvasDocument for a drawing.
Layers for drawing
A layer is an abstract drawing level, which acts like a sort of Z coordinate for a2dCanvasObjects. The idea is to put a2dCanvasObject's with the same layer id on the same abstract Z level. So for example: all objects with layer id 2, will be drawn on top of those with layer id 1. A a2dCanvasDocument maintains a list of layers, each a2dCanvasObject does have a layer id that refers to a layer from that list. The drawing order of the layers can be set by the user and changed on the fly. All objects on a certain layer situated in the nested a2dCanvasObjects are drawn at once, followed by the next layer in the order set etc. Using layers, the overlap of objects can be controlled. Changing the drawing order of the layers will result in moving all objects on a layer to the front are back. Layers by default work accros nested a2dCanvasObject's. So if there are nested childrens (where each canvasobject childlist contains objects on several layers) the drawing order of those objects will be as defined by the layers. All objects on a certain layer, inclusive objects in nested groups, will be drawn by traversing the whole document once. While traversing the document, all objects with the given layer id will be drawn. This is repeated for every visible layer. So children are using their own layer id for drawing, and this is independent of the layer id of the parent object. It is possible to set a flag in a a2dCanvasObject which will have the effect that all children objects will be drawn at once when the object itself is drawn. In that case a new layer iteration is started within the object, and the layers within the object and its childobjects are rendered in the right order. The layers are defined within a layer list containing a2dLayerInfo objects. This object is also derived from a2dCanvasObject, and because of this a layer list can be rendered, and maybe even more important stored into a file as part of a a2dCanvasDocument.
The a2dCanvasDocument is a tree like structure of a2dCanvasObject's, where the simple objects are the leafs on the tree. A a2dCanvasObject with children and a2dCanvasObjectReference make the branches in the tree structure. Those object's contain references to other a2dCanvasObject's in the form of simple C++ pointers. But in principle any a2dCanvasObject derived class which has pointers to other a2dCanvasObject's give shape to the hiearchy of the a2dCanvasDocument. The a2dCanvasObjectReference object has only one child which is a reference to another a2dCanvasObject, a reference has its own matrix to position the object that is referenced. This matrix is applied before the referenced object its own matrix. There may be any number of references to a certain a2dCanvasObject either from a parent a2dCanvasObject or a2dCanvasObjectReference, or any other type of a2dCanvasObject's which uses other a2dCanvasObject's internal. Reference counting is done within each a2dCanvasObject to make sure that deletion is done properly. References can save much memory when a whole grid of a certain object is needed. In this case the data for the object ( e.g. a a2dImage), is only stored once, the positioning within the grid is done by making references to one and the same a2dCanvasObject. Of course when a referenced object is changed, all references to it will display the change too.
(poly)Line End and Begin points
The reference system is also used to implement the begin and end objects for lines and polylines. For that, lines and polylines have two build in references to a a2dCanvasObject, which are used to draw begin and end points of the object. Any a2dCanvasObject derived class can be used for it. The begin and end point can use different objects. A scaling factor can be set which is applied as extra scaling for the begin and endpoint. So even if many a2dSLine objects share the same drawing for the begin/end points they still can be different in size. The End and Begin point a2dCanvasObject's, are aligned with the line, or with the polyline begin and end segments. Which means that the those objects are rotated untill its X axis falls inline with the line or polyline which uses them as Begin or EndPoint.
Due to the use of references, you can make references to a2dCanvasObjects outside its own a2dCanvasDocument also. This way it is easy to build a2dCanvasDocument objects containing often used objects. This i call a library. A good example is begin and end points for lines and polylines. It is possible to make copies of the object inside a library you want to use, but also only making a reference is possible. The layer settings of the a2dCanvasDocument library containing the object are used for rendering the object, instead of the layers setting from where the object is referenced. This is because the library objects have their m_root set to their own a2dCanvasDocument and therefore will used the layers settings defined by that particular document, even when the rendering routine is called from inside another a2dCanvasDocument.
a2dCanvasObjects can have properties attached to it. Properties are derived from a2dCanvasPropertyBase. Properties are kept as a list inside the a2dCanvasObject, and this makes them dynamic, so they can be added and removed on the fly. This is a great advantage when adding temporary data, like when editing an object. A property can be made visible using a special a2dCanvasObject called a2dVisibleProperty. This is added to the a2dCanvasObject containing the property as a normal child object, and takes care of displaying the property in text form, using the stringpresentation of the property. There are properties to add basic types like bool, int, double, string to an object. Other properties like a2dShadowStyleProperty and a2dClipPathProperty influence the rendering of its parent a2dCanvasObject. a2dCanvasClipPathProperty has a reference to a a2dCanvasObject, which will be used to clip the property its parent a2dCanvasObject object. It is possible to display the clipping object itself also, simply by adding it as a child to the a2dCanvasObject that is being clipped. All properties are derived from a2dCanvasPropertyBase and use reference counting, making it possible to use one property for many objects. Only a2dCanvasNameProperty derived properties do have a name, which can be set by the user. All a2dCanvasPropertyBase objects have an id to make them unique. Non a2dCanvasNameProperty derived properties do return their id as the name of the property. There are some reserved properties with names like "NAME_" , "OBJECTTIP" "URL" etc. Those are used by the a2dCanvasObject in a predefined manner. a2dCanvasObject can also have style properties (a2dFill, a2dStroke and a2dContour), again these properties are treated in a special manner, in this case to set the color, outline etc. of the a2dCanvasObject containing the properties. See a2dCanvasPropertyBase for a drawing.
Stroke andFill properties
a2dCanvasObject uses style properties as a2dFill, a2dStroke and a2dContour, to change the colour used for filling and outlining a shape. Those classes are wrapped into properties a2dFillProperty and a2dStrokeProperty, derived from a2dNamedProperty, and stored as normal properties in the propertylist of the object a2dPropObject. If no style properties are set, the layer settings will be used for styling the object. The layer settings define for each layer id a a2dFill and a2dStroke . The layer id of the a2dCanvasObject will be used to choose the style from the layer settings table. See Style properties for more.
The Clipping Property
A a2dCanvasObject has the ability to have a complex shaped clipping set for it. This clipping is defined by inserting a special property called a2dClipPathProperty. This property contains a reference to a a2dCanvasObject (also nested children are possible) which defines the area to which the object will be clipped. The clipping area is defined relative to the object. Therefore clipping areas are translated, rotated, scaled etc. with the matrix defined for the object itself. a2dDrawer2D intersects the currently set clipping path which is already set for it, with the new object clipping path, the result defines the new clipping area. There is a stack in a2dDrawer2D to push and pop clipping areas when walking up and down the document its object tree during rendering.
the name property
Objects do have a name, by default this is the id number that was given to it during creation. It can be changed if wanted, in those cases a reserved property with the name "NAME is attached to the object, which contains this name. In some cases it is stored as part of the object itself as a wxString member. The idea is to minimize memory usage when no name is set.
Each a2dCanvasObject has a mask containing a range of flags. For example the selected flag. Those flags can be set for a specific set of objects using one mask to select those objects, and another mask to define the new mask. This can be done recursive down a a2dCanvasDocument tree. In many other cases the mask is used to select or skip objects with the particular mask. A second mask is available to define how an object reacts to mouse events. It depends on stroke and fill types, and visibility if a a2dCanvasObject reports a hit or not.
searching and collecting
You can recursive search a a2dCanvasDocument object for a2dCanvasObjects having a certain mask, classtype, property etc. It is also possible to collect those objects and put them in a a2dCanvasObjectList. Next to this there are functions to move delete copy transform objects with a certain mask.
Graphs and Diagrams
Each a2dCanvasObject can be extended to be able to connect itself to other a2dCanvasObject's. This way a2dCanvasObject's can form diagrams e.g electronic schematic's. To connect a2dCanvasObject's, a2dPin's are added as children to the a2dCanvasObject's to connect. Every a2dPin in a specific a2dCanvasObject can connect to a2dPin's in other a2dCanvasObject's. This way objects can be connected with eachother, and moving such objects results in other objects being moved also or being resized to keep the connection intact. Building a library of objects which can be connected, is possible on the fly, since the only thing to do is place a2dPin's as childs. But you can generate such pins also by using program code if needed. So a2dCanvasObjects or connected through its a2dPin's, either using special wire a2dCanvasObjects that stretch as rubberbands, or directly to another a2dCanvasObject. Tools often take into account the fact that a a2dCanvasObject can have pins, e.g during a drag it will try to connect to other objects in the end. Also when moving a2dCanvasObject's apart, wire objects may be created to keep the connection intact. Because a2dPin's are normal a2dCanvasObject's, they will be stored to a file as part of the document when saving it in CVG format. See Connected Objects.