This document describes the design for the base Draw program. This document serves as a specification of how to realize the requirements of the basic-draw program. The requirements specification document is available by clicking at: http://www.cs.uwm.edu/~cs252/project/requirements.html This document will be used as a reference when designing and implementing extensions to the basic-draw. Requirements and design specification documents of extensions should refer to this document.
The Draw program is designed in accordance with object-oriented principles. The design aims to support extendibility and maintainability of the program. The rest of this document will focus on the architectural aspects of the design. An overall system architecture overview is presented followed by a discussion of each subsystem.
The Draw program has a group of subjects for handling different responsibilities. A subject is a set of responsibility-related classes. A subject may contain only one class. A class may participate in more than one subject. Basically, a subject is similar to a subsystem.
The following subjects are identified:
______________________________________________________________________________
Responsibility : Classes Involved
______________________________________________________________________________
Handling Canned Input : CannedInput Hierarchy, XMLTokenizer, Utilities
Handling Drawing Files : DrawFile Hierarchy, XMLTokenizer, unknown_class,
unknown_feature, load_exception, Error, Utilities
Handling Windows : SimpleWindow Hierarchy, WindowManager
Handling Domain Objects : Object Hierarchy, Bitmap
Domain Objects are those objects related to the physical world of Draw.
For example, Rectangle, Circle, All Tool classes, etc.
______________________________________________________________________________
The Draw program works by creating one instance of class Drawing. Each drawing has its own canvas window and own tool window. Mouse clicks in the window are delegated to the tools. Timer events are taken as times to provide canned input and refresh events are used to redraw the canvas. DrawFile works with XMLTokenizer to read input drawing files. Domain objects are created by the Object hierarchy either as XMLTokenizer, CannedInput, or DrawFile discover the features of the elements in the input file or interactively.
An object of class Drawing represents a complete drawing in Draw. A complete drawing is composed of a canvas, objects drawn on the canvas, and a tool window.
The Utilities, unknown_feature, unknown_class, load_exception, are support classes used by class XMLDrawFile. Class Error is local to class XMLTokenizer. The bitmap class is a support class used to display tools' icons. Class Utilities provides string manipulation services to the drawing loading process. The rest of the support classes will be discussed in the contexts of the subjects they support.
Canned Input is a facility that is helpful during testing. It allows users to describe scenarios of run sessions. Those scenarios can then be run more than once and automatically. Two classes cooperate to realize this responsibility. The class CannedInput that serves as a base class for XMLCannedInput.
Class CannedInput is an abstract base class. This is because we would like to be able to execute a canned input event in a uniform manner irrespective of the format of the canned input file. As of now we are only using XML format. Class XMLCannedInput inherits from CannedInput and defines how to handle execute events on drawings specified in XML-formatted canned input files. An XMLCannedInput object has an XMLTokenizer object to read in the different XML elements.
The CannedInput hierarchy uses services from class Drawing since an event specified in the canned input file would have to be applied to a drawing. For every command in the canned input the following object dialogue is carried out. The XMLCannedInput extracts a command using its XMLTokenizer. The command is executed by sending a message to the Drawing object. Then XMLCannedInput starts a timer in the canvas of the Drawing object. A TimerEvent happens after the amount of time lapses. A window (i.e. the canvas or tool bar) just waits for something to happen: in this case a MouseClickEvent, a RefreshEvent (when the window is resized, for example), or a TimerEvent and then reacts and returns to waiting. When this event is handled by the canvas it initiates executing the next canned input event. If there are no more events in the canned input, the canvas timer event handler stops the timer. So, the canned input is executed one event at a time every time a timer event occurs.
The events in the canned input simulate clicks in an interactive session. The most important are clicks in the canvas and clicks in the tool bar. These are simulated by calling the click function of Drawing. This function calls MouseClickEvent in the appropriate window. This procedure is
the entry for normal mouse clicks and thus the simulated mouse clicks
work the same as normal mouse clicks. It requires a little more
finesse to make sure that time elapses between simulated events. For
this reason, we pause for 10 milliseconds between each event unless a
larger pause is requested.
This is an independent generic component that provides services useful for handling canned input files and drawing files. The only responsibility is extracting tokens from XML files. Hence, the input to the tokenizer is an input stream and it gives back the tokens found in the file in the order they are encountered. The tokenizer discards comments so that it only returns tokens that represent XML elements, their attributes, or attribute values. The XMLTokenizer class has two clients, XMLCannedInput and XMLDrawFile. For canned input, the tokenizer extracts tokens from the input file and feeds them to the XMLCannedInput which takes care of creating events out of the tokens. This implies that the XML tokenizer does NOT know the ``meanings'' of draw terms. Understanding XML files requires dealing with the XML recursive structure, which is handled by the tokenizer clients.
Every domain-specific concept is represented by a class. Each of those classes inherits the class Object, which serves as a convenient base class for all objects in the program that can be loaded from or written to a file.
The requirements specify that the program should be able to read drawings stored in files. Two classes cooperate to realize this responsibility. The class DrawFile that serves as a base class for XMLDrawFile. The DrawFile hierarchy constructs an object of class Drawing from the input file. Support classes unknown_feature and load_exception are both exception types used to signal errors in input files' tags. An unknown_feature object indicating an error in an XML element's attribute is transformed into a load_exception object. This is justified by the fact that an error in an attribute should result in a loading error for the entire drawing specified in the XML file.
Class DrawFile is an abstract base class. This is because we would like to be able to read input files in a uniform manner irrespective of the format of the input file. As of now we are only using XML format. Class XMLDrawFile inherits from DrawFile and defines how to handle reading drawings from XML-formatted input files. An XMLDrawFile object has an XMLTokenizer object to read in the different XML elements.
A Singleton is a very commonly used programming technique involving static local variables. A Singleton function is incredibly simple; a static local variable is created and a reference to it is returned. Since the static local variable will not be destroyed until the program shuts down, it is safe to return a reference to it. The caller of the Singleton may then modify the local variable from outside the function. Essentially, a Singleton function is a global variable that must be accessed by first calling a function.
A singleton container used to hold the set of known factories is a static vector object created by a static (class) function of class Object::Factory which is an abstract base class. The constructor and destructor are protected members because derived classes need access to them to do their work. Meanwhile, they are not accessible outside this class hierarchy, preventing creating more than one instance. Hence, we get a singelton container.
In real life, a factory is a place where something is manufactured or
created. We have a class which serves a similar purpose. The
Object::Factory abstract base class is responsible for specifying the creation activities of objects of types derived from Object. Each factory is in charge of making a certain class of objects. It is registered with the
class name it supports, and called as needed when reading in a drawing file. The Object::Factory class uses both Singletons and static class functions to manage the creation of new objects. The Singleton container mentioned above serves as a registry of all the classes derived from Object::Factory. Whenever a factory object is instantiated, a reference to it is stored in the registry.
In the factory sense, an object of some class is a product, the corresponding factory class is the producer, and Object::Factory is a manager that allows us to deal with creating all objects uniformly irrespective of their actual type. Note that factory classes are only used during the loading phase of drawing files. The following factory classes are required to support the tools introduced above.
Circle::Factory
CircleTool::Factory
ColorTool::Factory
CutTool::Factory
Drawing::Factory
ExitTool::Factory
Group::Factory
MoveTool::Factory
MutateTool::Factory
Rectangle::Factory
RectangleTool::Factory
SelectTool::Factory
ToBackTool::Factory
ToFrontTool::Factory
The loading process involves transforming the textual representation of a drawing into a Drawing object suitable for program manipulation. Every XML element may have a set of features with associated values. Each element type is responsible for setting the values of its features using an add_feature mechanism. The classes corresponding to XML elements are all subtypes of type Object through inheritance (is-a relationship). It is convenient to have the load code delegate the task of adding features to Object, which redelegates the task to the target class through dynamic polymorphism. Class Object being the manager, is responsible for reporting errors due to invalid features trying to add themselves to elements. This is accomplished by having the add_feature mechanism at the Object level throw an unknown_feature exception. The add_feature mechanism shall be able to respond to different types of feature values, it does so by overloading.
The system has two windows, a canvas window and a tool window. Each one corresponds to a class. Canvas and ToolWindow classes both serve as parts of a complete Drawing object. Both classes inherit from SimpleWindow.
Class Canvas responds to mouse click events in two different ways depending on the situation. If a tool is active, it delegates the task corresponding to the tool to the appropriate tool class. Otherwise, it delegates a refreshing event to class Drawing. Note that class Drawing may ignore this request if a refresh is not needed. Class Canvas repsonds to timer events that are sent to Drawing which uses them to handle canned input. The canvas responds to refresh events whenever a change needs to be made visible on the canvas.
Class ToolWindow responds to mouse clicks by changing the current tool and delegating a refresh request to Drawing. The tool window indicates the current tool by highlighting it. The tool window keeps track of which tool is currently active and can report this information whenever needed. The tool window can handle refresh events to make changes to itself visible.
The function used to interface to the EZ-Windows library is ApiMain(). Windows are only opened if loading succeeds and produces a Drawing object. The canvas window is opened first, then the tool window. Class Drawing is responsible for opening both windows.
The following subsections describe the structure of the Object hierarchy.
A drawing has three parts: the contents (the actual objects to draw),
the canvas (where they are drawn) and the tool window. Each of these is an
instance of a nested class. Class Contents inherits class Group (see next section). Class Drawing itself inherits from Object.
Objects in a drawing can be selected. A drawing maintains a list of ``selected'' drawing elements. Class Drawing takes care of rendering the contents to the canvas. It also works with class Canvas to refresh the drawing canvas. Refreshing after every change can be slow, and so the drawing keeps track of whether refreshing needs to be done, and then after every user-operation
(mouse click) is complete, it refreshes only if necessary. If we refresh after each change, we can find ourselves rendering a draw element that is out of synch. Imagine mutating a rectangle and having it briefly appear with just the height changed and not the width.
To decide when refreshing is necessary, class Drawing keeps a flag that indicates whether a refresh is needed or not. Drawing objects, groups of drawing objects, and tools are the entities that undergo transitions that need the drawing to be refreshed to reflect changes applied to them. For example, If a circle is mutated or moved, a group has changed its color, or a tool is selected. All those changes require the drawing to ``show'' them, i.e. refresh its canvas or tool window.
Actual changes are produced at the drawing object level (e.g. changing a circle radius), however, they are produced by using the tools (e.g. mutate tool). The tools classes delegate the task to class DrawElement through class Drawing. Class DrawElement takes care of a change by delegating the task to the specific drawing object (e.g. circle). Now, the only one aware of a change is the drawing object because it executed it. In order for the change to be visible, a refresh is needed. So, the drawing object informs the group it is a member of that a change occurred. The group informs its enclosing group and so on till the outermost group knows about the change. The outermost group is the object of class Contents group in class Drawing. Contents now delegates to Drawing the task of making the change visible. It also causes the Class Drawing refresh flag to be ON indicating the need for a refresh. This arrangement generates a refresh event on the appropriate drawing component (canvas/tool window).
A drawing element is a part of a drawing. At any point during the lifetime of a drawing element, it has to belong to some group of Drawing elements. Class DrawElement supports generic functionality applying to elements. This includes adding/removing to/from a group, changing position on canvas, changing color, and rendering itself on the canvas.
Class DrawElement serves as a base class for all drawable shapes' classes. Currently, we only support circles and rectangles. Also, it is a base class for class Group. A group is composed of draw elements, this is an aggregation relationship. From a functional point of view everything that can be done by an element can certainly be done by a group as a compound unit. This suggests an inheritance relationship whereby class Group reuses the behavioral interface of class DrawElement. Another way to look at the design logic here is to think of a group as a drawing element of drawing elements.
Every operation that can be invoked by the user is modelled as a class. Each class is a derived class of the abstract base class Tool. All tools have to be activated and deactivated. However, the operations involved depend on the specific tool functionality. That is why an abstract base Tool class provides a uniform means for activating/deactivating tools while retaining their specific details. The same argument holds for how each tool responds to mouse clicks.
Tools are rendered to the tool window by loading the corresponding bitmap images. A bitmap image file is represented in the program as an instance of class BitMap. Bitmaps are not only used during loading, but also when handling refresh events on the tool window. Each tool knows what bitmap image corresponds to itself. Each tool knows which drawing it belongs to. This information is kept at the Tool level and inherited by every other specific tool class. The following are the Tool classes as mandated by the requirements.
CircleTool: For drawing circles.
ColorTool: For changing colors of drawing elements.
CutTool: For cutting drawing elements.
ExitTool: For terminating a basic-draw session.
MoveTool: For moving drawing elements around the canvas.
MutateTool: For changing sizes of drawing elements.
RectangleTool: For drawing rectangles.
SelectTool: For selecting drawing elements.
ToBackTool: For sending a drawing element to the background.
ToFrontTool: For bringing a drawing element to the foreground.
The detailed design provides a class-level specification. This is provided online at
http://www.cs.uwm.edu/~cs252/project/hierarchy.html. This page is composed of a set of links to html documents each of which specifies a detailed class design.