CanvasModule Topics
Contents
Reference counting
Several classes in the wxArt2D Library use a simple reference counting system. Which makes it possible to use the same instance of an object in many places at the same time. Although more advanged smart pointer systems do exist, this one is taking the minimum of memory. This reference counting system is based on many pointers to one object. Therefore it works also with a polymorphic list of some base class. When an object having reference counting is constructed, its m_refcount is set to zero. A class instance that does create the object is responsible for in-crementing the m_refcount. The parent class is owning the object, and the function to call is BaseClassPointer->Own(). In general the same instance is later also responsible for de-crementing the m_refcount, which means that the owning class releases the object. For that it calls BaseClassPointer->Release(). Any other object that wants to make use of the same object, can decide to do the same. The last parent object which releases an object will also automatically delete that object. This happens when the reference count has reached zero, leading to the object its destructor being called. In the end this simple mechanism makes it possible to use the same object by pointers in many places, which is ideal for a2dCanvasObject and other a2dObject derived objects. Very convenient is having stock objects like a2dCanvasNullObject, which can be used by all pointers/reference to a2dCanvasObject. If such a reference is not in used at the moment, the reference will point to the NullObject. It prevents using NULL pointers and treating those in a special way, since now it is just a matter of changing the reference from one to another.
During creation or another use of the same object, DO use: object->Own()
During releasing/deletion the above parent object, DO use: object->Release() //maybe deletes the object, but surly decrements the refcount.
Even if an object is reference counted, you may use a pointer to such an object without calling Own and Release, but only you must be absolutely sure that the object is not deleted in between, which of course will lead to a dangling pointer.
Style properties
a2dCanvasObject uses a a2dFill and a2dStroke, to change the colour and manner used for filling and outlining of a shape. Only the a2dDrawer2D class knows what to do with them on a specific drawing device. Next to the common style cases used in the wxWidgest classes wxPen and wxBrush, a2dDrawer2D supports styles for drawing transparent and gradient types of filling. The a2dFill and a2dStroke are classes which wrap all types of filling and stroking. a2dFill, a2dStroke are derived from wxObject, and implemented using reference counting on the basis of copy on write (COW). This means that when an object is assigned to another, no copying is done, only a reference count is incremented. As long as the object does not change, the internal data is shared. But as soon as an object is changed using its member functions, the internal data is cloned, and only the changed object will get the change its data. All other object referencing the same data will not be influenced. This is fast, and makes it easy to deal with style properties which are shared.
When setting a a2dFill etc. to a a2dCanvasObject, it will not be added directly, instead it will be wrapped into a a2dFillProperty. a2dCanvasObject is derived from a2dPropObject, and the fill property will be added to the propertylist.
Wrapping style into a dynamic property, makes sure no memory is used if there is no specific fill or stroke to use for a canvas object. In such cases the style is based on the style of the layer where the object is on.
Because of the reference counting one and the same a2dFill etc. can be shared by many a2dCanvasObject's, reducing memory usage. Especially reference counting using copy on change, makes it easy to reuse fill and stroke objects without effort. Setting a certain stroke or fill to a a2dDrawer2D, means only giving it a reference to a given fill or stroke. This is what makes a2dDrawer2D fast. Especially when sequential objects to draw, are using the same stroke and fill (like objects on layers without a private style), a2dDrawer2D does not actually change the fill or stroke internally. It does check first if the new fill or stroke is not the same as the current one. If it is the same as the current stroke/fill ( comparing if internal m_refObject data pointers are equal not its contents, and therefore fast ), nothing will be done. For a wxDc based a2dDrawer2D this means no change in wxBrush or wxPen, which saves much time.
Stroke width is not directly part of the calculated boundingbox. When a boundingbox is asked for, it will be included at that moment. The reason for not storing the stroke size within the boudingbox is: not being able to transform a bounding box to an absolute position when a stroke is included. The width of a stroke in x or y can and should NOT be different while rendering an object with an outline. The windows and xwindows API always uses the same stroke width for the outline of a polygon in all direction. Still a group of objects or a reference can be scaled different in X and Y and therefore the a2dCanvasObjects in the group are drawn scaled also. To have the right clipping areas the scaling of the parent is first applied to the object its boundingbox without the stroke included and after that the stroke width is added. A stroke is always fixed in size and does not scale when the object itself is scaled, and should therefore not be affected by transformations. So the stroke width will also not be scaled by the parent objects. The stroke width must be seen as a real world pencil, no matter how small are big you draw an object on paper, the width of the pencil its stroke width used to draw it will not change.
Another reason to add stroke width to the bounding box on the fly, is to support pixel strokes. Stroke width can be defined in world coordinates or in pixels. In pixels the pen width is independent of the mapping from world to device coordinates. So if the same document, containing an object with a pixel stroke, is displayed on two views at the same time, its size on the view in pixels is fixed. As a consequence the bounding box of the object in world coordinates must be different for both views, in order to clip such an object properly. Even if the object itself is defined in world coordinates and is fixed in size, due to the pixel stroke, its size in world coordinates will variate in world coordinates in the end. Since bounding boxes are always stored in world coordinates, the width in world coordinates for a pixel stroke can only be calculated when the a2dDrawer2D to be used for the mapping is know. So for those pixel stroke objects the real bounding box variates with the a2dDrawer2D that is active.
The above leads to the problem of calculating the bounding box for a2dCanvasObject with groups of children containing objects with pixel strokes. Such a group will/can have a different bounding box on each a2dCanvasView, simply because one of its objects is pixel based in some manner. Therefore when updating the bouding box for a a2dCanvasObject the maximum child extend is detected, and added to the extend of the parent object. The same trick is used to implement pixel defined a2dCanvasObjects.
To take care of all the above discussed problems, there are two extends, one for world extend, and one for pixel extend. Anything not within the untransformed and clean bounding box will be put in there. Those two extends will be added on the fly when the complete bounding box is needed.
A a2dCanvasView has a drawStyle itself, which will overrule all fills and strokes, this makes it possible to drag groups of objects in wxINVERT mode. Several other modes for drawing only outlines are available.
a2dDrawer2D is not aware of the fact that a a2dCanvasDocument is organized in layers, and only knows about its current fill and stroke. The a2dCanvasObjects in a a2dCanvasDocument make sure fill and stroke are set correctly when rendering is done on it, either using its layer id or private style properties. This will be done for each a2dCanvasObject in the document, since it is part of the base class rendering routine of each canvas object. The "DoRendering" routine of a derived a2dCanvasObject, can decide to stick to those settings, or change the style to whatever is wanted/needed. If the user decides to write an object that is a "Red circle with white dots always", clearly the default style settings should be ignored or at the least for the white dots an additional style will be set to the active a2dDrawer2D. For all basic objects like circle, polygon etc., the layer or private style properties are used. For more complex object's that require more then one style property to present itself properly, one can decide to overrule or add additional style member functions for certain features of the derived object.
A a2dCanvasObject also can have a shadow property, having its own style. When a shadow property is set, the object is simply rendered twice. First with a shadow with an offset and an angle, next the real object on top of it.
Events and Hittest on objects
Events are intercepted in a a2dDocumentViewWindow window and redirected to a2dView. Those two classes are the base classes of a2dCanvas and a2dCanvasView. And a2dCanvasView has an extended event processing which redirects the events to the ShowObject, which is a a2dCanvasObject. From there the a2dCanvasObject redirect it to the child a2dCanvasObjects, and so on. While descending into the document, hit tests are done on the canvas object, and based on that new events are generated. Such events are special mouse events for the a2dCanvasObjects that are hit by the mouse pointer. Amongst which are events that tell if the mouse pointer leaves or enters a a2dCanvasObject. The events traverse the document in the same manner as when rendering a document, only for layers it is in reverse order. This makes sure that object drawn on top are hit first. A a2dCanvasObject can also Capture all events which are sent to the a2dCanvas window. A a2dCanvasObjectHitFlags mask is available to define how an object reacts to (mouse) events. It depends on stroke, fill style, and visibility. The user can define a static event table to intercept and handle events, the mechanism is the same as for wxWidgets's in general.
Often in interactive tools it is needed to know if the mouse pointer hits ( is within ) a a2dCanvasObject or nested child. Hittest is the routine which checks if a certain (x,y) position is within a specific a2dCanvasObject. For a hittest on a a2dCanvasObject containing children, the objects inside the child list are tested for hit also. They are seen as part of the a2dCanvasObject itself. When a hit is found the top object which is a direct child of the ShowObject of the active a2dCanvasView is returned. The hit test routine for each specific object is designed to be accurate, and not only a bounding box check. Different strategies are possible, for instance which objects on the top layer is closest to the mouse pointer. For the moment the layers are searched in the reverse order as set by the a2dCanvasDocument its layer settings, and the first object hit on a certain layer is returned. If the object hit is inside a deeper nested child or reference, also the objects leading to that particular object are reported as hit. In fact a list of objects is returned next to the actual top object hit.
The above hit test routines are also used in the event processing routines of a2dCanvasObject, this is how it knows if a certain event needs to be generated when the mouse pointer is within it, or it is captured.
Event Distributer
wxArt2d uses an event distributer called a2dEventDistributer, to notify changes from one object to another. The event distributer is derived from wxEvtHandler. It used also in the docview module, on top of which wxArt2D is build. Events are handled in the overriden ProcessEvent( wxEvent & event) function of the derived wxEvtHandler. One can register a class instance (which needs to have as base class wxEvtHandler). to the a2dEventDistributer. All registered class instances will receive all events that are sent to the a2dEventDistributer itself. So when a a2dCanvasObject class sents an event to the event distributer, telling that its filling style has changed, all registrated class instances will receive this event, and modify itself to show the new filling style. Imagine a tool to edit a2dCanvasObject's, at the same time there is mode less dialog to set the filling style for the object that is selected for being edited. Every time the tools selects a different object for editing, the mode less dialog needs to show the filling style of that particular object. So the tools sents a setfill to the a2dCanvasCommandProcessor where the current style settings are stored. This leads to a fill change event being generated and sent to the eventdistributer. The style dialog has registrated itself, and therefore will receive that event, and react by redisplaying itself with the new filling style. On the other hand while editing is in action, the user may decide to change the filling color of the object being edited. In that case the style dialog does sent the fill color command a2dCanvasCommand_SetFill to the a2dCanvasCommandProcessor of the document that is currently active. The command calls a2dCanvasCommandProcessor::SetFill again, generating the fill change event for the event distributer. The a2dCanvasObject editing tool which has registrated itself also, will now receive the event, and update its internal style settings. The object being edited is also know to the a2dCanvasCommandProcessor sending a Fill command will automatically set the new fill style to the current a2dCanvasObject, which is the object being edited. While drawing new objects the situation is similar. The above involves only two class instances, but often several classes need to know the current style in use. Even if a class instance has registrated itself, it can still sent events to the event distributer, the events are never sent back to the sender itself. A Class instance can also unregister itself. Often a class registers itself in its constructor, and unregisters itself in its destructor. But this is not a must, one can also temporarely register a class instance to the event distributer. For the moment there is only one global event distributer, more should not be a problem. A registrated class does get events sent to it, but in order to intercept the event, they need to be added to the static event table of the class. Like in the next example, EVT_CANVAS_EVENT is the type of event that gets sent to the event distributer. First a class makes a new a2dComEvent, called changed. It sents this to the event distributer as a pending event. Direct processing of the event using ProcessEvent is also possible, which may be preferred in other cases.
a2dComEvent changed( this, m_fill );
a2dDocviewGlobals->GetEventDistributer()->AddPendingEvent( changed );The event just sent to the event distributer as being pending, is distributed across the registrated handlers in idle time.
A2D_BEGIN_EVENT_TABLE(a2dObjectEditTool,a2dStTool)
A2D_EVT_COM_EVENT( a2dObjectEditTool::OnComEvent )
A2D_EVT_MOUSE_EVENTS(a2dObjectEditTool::OnMouseEvent)
A2D_EVT_IDLE( a2dObjectEditTool::OnIdle )
A2D_EVT_CHAR( a2dObjectEditTool::OnChar )
A2D_EVT_UNDO( a2dObjectEditTool::OnUndoEvent )
A2D_EVT_REDO( a2dObjectEditTool::OnRedoEvent )
A2D_EVT_DO( a2dRecursiveEditTool::OnDoEvent )
A2D_EVT_KEY_UP( a2dObjectEditTool::OnKeyUp)
A2D_END_EVENT_TABLE()The event is handled in a2dObjectEditTool::OnComEvent, which will test the event to see if it is an event it wants to process. Assume the editor tool is currently active editing an object, and the event is to change the fill style. This event was sent from the command processor, to tell all interested that the current fill has changed. The editor tool will intercept the event, and do whatever is needed, to reflect the change in fill.
Onpaint and OnIdle
In a multiple document application, a a2dDocumentFrame redirects events to its a2dView. a2dView in some cases. a2dView by default redirects some events to the document. In any case the display window for a a2dView, e.g. a2dDocumentViewWindow or a2dScrolledWindow, is always redirecting its event to a2dView first. So idle events will also reach the a2dDocument class. a2dCanvas is derived from the a2dDocumentViewWindow. When a2dCanvas is used standalone, idle events are also intercepted by a2dCanvas and redirected to the a2dView. Idle events are used within wxArt2D to update changes to the a2dCanvasView buffer and blit those changes from there to the a2dCanvas window. Changes to a a2dCanvasObject are reported to the a2dCanvasDocument, and in idle time the areas displaying the changed object are re-rendered to show those changes on the views.
Next to changes on the document itself, resulting in re-rendering, there is also a redisplay of damaged parts. Damaged parts, are a result of (re)placing other windows or dialogs on top of the a2dCanvas window. As soon as such a window is removed or replaced, onpaint events are generated. The a2dCanvas window its duty is to redraw what should be in the uncovered areas of the window. Since a2dCanvas uses double buffering, redisplaying damaged areas is just simply blitting those areas from the a2dCanvasView buffer to the window again. The windows API which generates the Paint event is not aware of changes in the document itself. In order to make that the buffer that will be blitted to the window is up to date, the Onpaint function first re-renders pending update areas. The updatelist containing the update areas is maintained within a2dCanvasView. As soon as the area is the updatelist are rerendered, the buffer of a2dCanvasView is up to date. But the same areas still need to be blitted to the window, This is indictaed by a flag. Now something to take care of is, that parts that where already in the updatelist are ignored in Onpaint, since Onpaint only blits what it thinks are the damaged regions. Other updates areas outside the onpaint region list are simple ignored by Onpaint on certain platforms (e.g. winNT). This happens when the frame has the style wxNO_FULL_REPAINT_ON_RESIZE. which is of course prefered for speed. All the above does not give problems since as soon as the program becomes idle, a2dCanvas::Onidle will blit the remaining areas that where not blitted yet. Although this strategy may result in a few extra blits (not updates!), this makes it work with and without the wxNO_FULL_REPAINT_ON_RESIZE style set.
Directly updating the whole buffer when a paint event is handled, is needed because wxWindow/Control objects can be part of a a2dCanvasDocument. Those control object are not rendered to the buffer at all, and need to be refreshed. A refresh result in a paint event, repainting the controls on the windows. So blits of the a2dCanvasView buffer to the a2dCanvas window, always need to be followed by a refresh in order to repaint the controls on top of it.
Drawable objects in Invert mode
Objects must be able to draw itself in wxINVERT mode. Drawing twice the object in wxINVERT mode must make it invisible again. If this is not automatically the case when drawing in wxINVERT mode , a simple representation of the object is drawn to asure this.. For example a wxImage in wxINVERT mode is drawn like a rectangle only. When the m_filled flag for the object is FALSE, only the outline of the object is shown. Which in most cases means a transparent brush is used. This is not always the same as drawing the object in invert mode. a2dDrawer2D also has a drawstyle that can be set, this overrules the settings for the a2dCanvasObjects. This drawstyle is used for tools that drag objects in wxInvert mode.
BoundingBoxes
A boundingbox a2dBoundingBox is a rectangular area alined with the horizontal and vertical axis of the used coordinate system. This coordinate system can be relative to another one. In wxArt2D this relative coordinate system is the coordinate system of a single a2dCanvasObject. If a a2dCanvasObject is a child of another a2dCanvasObject, the first relative coordinate system will be transformed by the matrix of the parent a2dCanvasObject. In case of several levels of nested child objects, this happens several times like this. In reality a accumulated matrix is calculated while traversing the a2dCanvasDocument its nested children. This matrix can be used to directly translate the relative coordinates of the nested a2dCanvasObject, to absolute world coordinates. The boudingbox is only calculated for the object within its own relative coordinate system. The relative boundingbox after transform to an absolute position may change in size as result of rotation and scaling. The boundingboxes are used mainly for clipping or rejecting objects for redraw, when an area on a a2dCanvasView view needs an update. This means that the absolute boundingbox will be intersected with the area needing an update. If those two intersect, there will be a redraw of that area.
The datastructure stored in a a2dCanvasDocument uses a2dCanvasView views to display itself within them. The a2dCanvasView normally does not influence the a2dCanvasObject's itself, unless some dimensions are defined in device coordinates instead of world coordinates. For instance, this is the case when using a pixel stroke. There are two extends available in a a2dCanvasObject, one is the world extend and the other the pixelextend. Both are not part of the relative boundingbox, and added only when the complete boudingbox is asked for. See also Section 5.2, “Style properties” to know why this is needed. The same a2dCanvasDocument can be displayed on several a2dCanvasView objects at the same time, all the updating is under control of the a2dCanvasDocument class. Changing a2dCanvasObjects in the a2dCanvasDocument will result in the right parts being updated in the a2dCanvasView objects that are in use to display that particular a2dCanvasDocument. Every a2dCanvasView can have a different zoom and different a2dCanvasObjects residing in the same a2dCanvasDocument can be displayed on seperate a2dCanvasView's, therefore the object using pixel defined extends will be different in size on each a2dCanvasView. This is true for a2dCanvasObject that has a stroke defined in pixels, but also a2dHandle uses the pixelextend, since it is totaly defined in pixels.
Boundingboxes do not have the stroke width included. The reason is that the stroke can be defined in world or in pixels. In pixels, the boundingbox in world coordinates can only be calculated when the a2dCanvasView is known. Because of this, stroke width is always added to the extends, either the worldextend or the pixelextend, depending on the type of stroke. Those extends are added on the fly when asking for the boundingbox of the shape including its extends. If the a2dCanvasObject is itself a child of another a2dCanvasObject, the last a2dCanvasObject will have a boundingbox without the extends too, but its extends will be the maximum extend of its children and itself. This is needed since that boundingbox needs to be enlarged with the maximum stroke or other extend available in its child objects. Extends are added to the boudingbox as a positive offset. It assumes extends are always eqaul in all directions. This is perfect for most uses. At the maximum the calculated boudingbox including the extends, will be a bit bigger then is strictly needed.
Each Object has only one position/transform which is relative to the parent(s). When placed within a parent Object, the parent decides how this relative position is used. Seen from the parent of a a2dCanvasObject as a normal child object, the child object position is added to the position of the a2dCanvasObject itself. Actually it is not only position, it includes scalling and rotation also. The Boundingboxes for a a2dCanvasObject is calculated relative to its parent(s). But the boundingbox is the same for all parents. The boundingbox is used for clipping, hittest etc. Having a boundingbox saves much time, since if the boundingbox is outside the area to redraw, there is no need to go through the contents of the object, it can simply be skipped. The member function a2dCanvasObject::GetAbsoluteArea() calculates the area occupied in device coordinates by first transforming the boundingbox of the object using the supplied matrix. The suppplied matrix is the accumulated matrix which transforms the object data, the boundingbox here, to an absolute position. After that it calculates the new resulting boundingbox, which is aligned with the absolute coordinate axis. And know the boundingbox gets extended, with the extends. First the worldextend, and after conversion to device/pixel coordinates the pixelextend is added. The following picture Boundingbox mapping demostrates this.
Boundingboxes of a2dCanvasObjects must always be calculated before rendering, it is a seperate action. The rendering needs to know the boundingbox of the object to render to see if it falls within the area to render.
They are NOT calculated when creating a a2dCanvasObject or at the moment it is required. The same is true for its extends in world or pixel coordinates. This for the following reasons:
- The bounding box is part of the state transition. When an object has changed position, the old bounding box is used to redraw all objects that are located at the old position of the object. After that the new bounding box is calculated, and the area defined by the new bounding box will be re rendered also. The redrawing of the old and new bounding box areas, is only happening after all areas to redraw are assembled.
- It saves time.
Some a2dCanvasObjects have a extend that is (partly) defined in pixels, still it always needs to deliver world coordinates when asked for the absolute bounding box. Since the object is a certain amount of pixels, the bounding box in world coordinates variates, it is depending upon the displayed part in world coordinates on a particular a2dCanvasView. For non pixel extended objects, the total bounding box that is calculated is fixed in world coordinates and not depending on the a2dCanvasView that displays the data. The extends is always added when the bounding box is needed completely and the drawer is known. But for object and its parent objects the extends are already know, they only need to be added.
Changing an object in such a manner that its bounding box changes in position and/or height and width, may result in changing the parent object its bounding box also. So a recursive call from the a2dCanvasDocument for calculating the new bounding boxes will always be needed when changing one object in the database. When the object bounding box is invalidated, calling Update from the root object, results in recalculating only the bounding boxes of parent objects that contain the object. Actually when a bounding box is invalid, this is reported to the parent, resulting in recalculation of the parent bounding box also. So bounding boxes not having the object as a child (deep down), will not be recalculated. Although Recalculating all bounding boxes can be forced if wanted.
Layers
a2dCanvasObjects are on layers (default layer is wxDEFAULT_LAYER 0). Even a plain a2dCanvasObject can have a layer set. a2dCanvasDocument::Render() iterates over layers, and for each layer goes through the whole data structure to render that layer. If there are 10 layers, that same object with children will be rendered 10 times, once for each layer, but only those objects on the layer (having the given layer id) to render are actually rendered.
The Order of drawing the layers can be set in the a2dLayers class that is attached to a a2dCanvasDocument. Each a2dCanvasDocument has his own layers settings. A layer has a default a2dFill and a2dStroke, which the a2dCanvasObject may use to render itself on the layer, but this is not a must. Objects can Render itself using any kind of fill or stroke. For speed it is best not to change stroke and fill to often. Because a2dDrawer2D expects pointers for setting fill and stroke, this is optimized easily, only when the pointers differ from the current fill/stroke pointer, something will really be changed inside the a2dDrawer2D. For example if all objects on a certain layer use the same fill and stroke, they will be set all the time, but inside the a2dDrawer2D nothing is really done. To optimize the use of inherited stroke and fill from the parent object, the a2dCanvasObject uses a trick to minimize switching the fill or stroke. It compares child and parent style, and switching will be minimized like that, since when the next child has the same style as the previous, the style of the parent will not be restored in between.
An object may contain other objects or references to objects (e.g endpoints of lines). Those object are/should be situated on the same layer as the parent object itself. In case you want to be able to use complex object ( having nested childs ), you must make sure to start another layer iteration. Setting the m_flags.m_childrenOnSameLayer for those objects will make this happen. But do this only temporarely when rendering the object. Still there might be situations that it is better to program another layer iteration loop.
A a2dCanvasObject is in charge of Rendering itself in any colourfull picture it likes. It can use layer fill and stroke, or when a style property is available the objects its own fill and stroke. And if wanted, non of the above but a fixed fill/stroke. The base Rendering routine always first sets the fill and stroke for the active a2dDrawer2D, based on layers or on the object style property. After that the DoRendering routine is called, in there the programmer is free to do what he wants. As an example when editing shapes, handles that are part of the shape always use the style of a global defined handle style.
a2dCanvasObjects are normally rendered from the parent, at the top starting at the ShowObject of a a2dDrawer2D. The a2dCanvasDocument controls the order of layers to draw. The a2dCanvasDocument iterates over layers, after it sorted the a2dLayers on m_order of the layers that are available (the layers which contain objects). So when the object to draw has the right layer it will be drawn from the parent object. The a2dCanvasObject does check the layer itself. If a a2dCanvasObject is Drawn, Objects like line ends that are seen as part of the shape will be drawn also. You may choose a different style/layer for those objects, but they will be drawn at the same time as the object itself. The trick explained earlier is taking care of this.
Layer id wxLAYER_ALL, has a special meaning and is never part of the layer list. If the layer id to render is set to wxLAYER_ALL, all nested object will be drawn at once, ignoring the layer order in the layer setup. The style for the layers is still set as the object demands, only the order of layers is ignored. This is good for speed, and used when drawing only outlines for example. It is possible to decide to place a whole a2dCanvasObject plus children on a layer itself, by setting the flag m_childrenOnSameLayer for the a2dCanvasObject. Now all objects in the childlist (also nested) will be drawn at once when the layer that is set for that a2dCanvasObject is being drawn. Even if the objects in the objects in the childlist do have layers id's different from the parent, the whole childlist will be drawn at once. To have the child object in the right drawing order too, while drawing the objects in the childlist, an extra iteration accros the layers is started, but only for the the childlist that is currently drawn. Once the complete childlist has been drawn on the parent its layer, the drawing process continues for other objects and layers in the current parent of the childlist that was drawn just now. Layersettings for a a2dCanvasDocument, also define a fill, stroke and contour, which is used to define the style of the a2dCanvasObject's on a specific layer (objects having that layer id). This can be overruled by setting style properties to a a2dCanvasObject. The wxCanvasPropertyBase derived classes a2dFill, a2dStroke, a2dContour can be added to a a2dCanvasObject's its property list. Instead of the layersetting its style properties, those private style properties will be used instead. Before rendering a a2dCanvasObject, the fill, stroke and contour for the a2dDrawer2D will be set, either based on the style properties if available, or else as defined by the layer id of the object and the wxCnavsDocument layersettings array. See a2dCanvasDocument storage for a drawing.
The flag values m_flags.m_prerenderaschild and m_flags.m_IsProperty are used by a parent object to define when the child object will be rendered. Depending on those flags it will be either rendered before the rendering of the parent object itself or after. You should only set one flag true. The property flag, is used for displaying property editing object, and are rendered after all others.
Parsers for reading and Output formats
Reading data into a a2dCanvasDocument from a file can be implemented external to the class, using special parser classes. This parser class reads the data stored in a file and maps it to objects that can be stored in a a2dCanvasDocument. Understand that the input file format can be structured completely different, and a one to one mapping to a2dCanvasObjects is not always possible. Several objects in the input file may be combined in one a2dCanvasObject class or visa versa. Writing data from a a2dCanvasDocument to a file can be an integral part of the a2dCanvasObjects that are stored in the a2dCanvasDocument class. This is the case for the CVG format which is specially developed for the wxart2D library. For CVG there is a member function that stores the contents stored in the a2dCanvasObject as XML. Another possibility is to use a a2dDrawer2D, but this is only possible when the output can be written in simple primitives like polygons, circles etc. A third possibility is to traverse the a2dCanvasDocument from the outside, and for each object encountered, write what is needed to a file are window. This is ideal for scripting languages to add extra output format's without the need to extend the library itself. Here the traversing routine needs to know how objects are organized internal in the a2dCanvasDocument and ask data using the member functions of the object. What helps is the dynamic class system, which gives the traversing routine a way to tell which object is found, and on the basis of that decide how the information contained within the a2dCanvasObject needs to be translated to a file. The main problem is that if the library is extended by user objects, or the user just derives new objects in his own code, those traversing routines need to be updated also. But if those output generators are part of the Library this is not possible. The solution is to use a list of handlers, each object which needs to be written by the format, inserts a handler in the output generator. The handler has a pointer to a function, which will be used to write the object to a file. wxCanvasWalkerIOHandler is designed to do this. It can also be used to add algorithm to existing document structures. And all depends upon the program or library you are writing, to choose the best method. a2dIOHandler is the base class, for new IO handlers.
Currently there are several formats for in and output. The CVG (canvas vector format), is specially designed for the wxArt2D library. It uses dynamic object creation to regenerate what was saved in a a2dCanvasDocument. Another is SVG (scalable vector graphics), which is a new w3c standard. Both are XML based, and the input parsing is done with the XML parser Expat. The CVG has a one to one mapping to the objects that can be stored in a a2dCanvasDocument. New user defined objects can be made part of this. Those new objects will and can only be read back in by another application which uses wxArt2D, when those new objects are known in that application also, else they will be ignored. Extending the library will not directly lead to other applications failing to read the format, it will just skip the unknow objects in there. The CVG format stores the class names as part of the format, and uses this information later to construct the same class instances again. WxWindows has a nice system for constructing object based on having the name of the class as a string. Currently all basic primitives can be saved and loaded with this format, including nested objects in groups. The document layer settings are saved as part of the CVG format.
The way to add additional formats, as part of the library, is to use the traverse functions for the a2dCanvasDocument in a derived a2dIOHandler. And if needed/wanted the format can use a combination of members functions and iterations. The problem will always be, that 100 formats may be possible to output, and for each format a member and iteration like done in CVG can be added to each a2dCanvasObject. But adding a new type of canvasobject would become hard, since one has to write output members for those 100 formats. Next to that, one is often only interested in a few. And at last switching a library in and out will be hard to do and maintain.
