The major classes and their relationships are shown here. The State and Trans store non-derived information; these alone are stored and retrieved from disk. Instances of the StateImage and TransImage classes, derived from instances of State and Trans, describe how states and transitions are currently being displayed. The View class surrounds a canvas widget with the additional information required of a particular view of the database, including a list of all StateImage and TransImage instances displayed on the canvas. The Editor class surrounds a View instance, and is responsible for tool selection, controlling what appears in the View, and so forth.
The State Class
The State class contains
This can be specified at creation
This is specified at the state's creation.
This list is maintained by two methods, attachChild and detachChild, called by the constructor and destructor for this method. This scheme requires a state's parent to exist before the state can be created. To ensure this when a database is read, the states are written in a tree order.
(a) circular (cx = cy = 0.5) (b) a rounded rectangle (cx = cy = 0.1) (c) rectangular (cx = cy = 0)
In one of my early attempts, I attempted to place this information in a separate class (see the Dead Ends section). For performance reasons, it was placed in the State class. If additional view types were added, the State class would grow cluttered---a clean separation would prevent this.
Although this information can be recreated from the transitions themselves, it is stored explicitly here for performance reasons---when a state moves, it must inform all of its attached transitions about the move, since they will need to update themselves.
This information is not saved---the transitions themselves keep their state's lists current using the methods attachTrans, detachTrans, attachTransD, and detachTransD. The constructor and destructor for the Trans class call these methods.
When a state's information changes (e.g., its position), all attached StateImages, which are displaying the state, are informed of the change by the config method defined on the State class. For a single view type, such as is currently implemented, this scheme is easiest, but as view types increase, this will become error-prone.
The constructor and destructor for the StateImage class calls the methods attachImage and detachImage defined on the State class to maintain this list. This is the best time to update this list, but including it explicitly in the routines is error-prone.
These methods are used by the constructors and destructors of the other classes to maintain the various lists.
The procedure that writes the database to disk use this method.
Only those values which differ from their defaults are written out explicitly to save space. One danger of this is if the defaults change. A better way would be to write out the defaults at the beginning of the file.
These call the select and unselect methods for any attached StateImage in a given view---selection is by view, not global. Currently, these are slow because the view of each attached StateImage must be compared with the requested view.
These are called by the select and unselect methods of the view class.
Moving a state is a common operation, so I wrote a special method for it. This moves the two coordinates defining the state's bounding box, the position of the name, instructs all its child states to move themselves, and informs each of the transitions that they have moved using the moves and moved methods.
No single canvas object was rich enough for what I wanted to display, so I built one. These IDs are used when the state changes; the config method modifies the coordinates and attributes of the canvas objects to correctly reflect the state of its master State.
The four control handles allow the user to move each of the four corners of the state. The name handle allows the state's name to be moved. The control handle allows the user to adjust the shape of the state, as shown here:
Control for a state's shape: (a) circular (cx = cy = 0.5) (b) a rounded rectangle (cx = cy = 0.1) (c) rectangular (xc = cy = 0)
Since the handles only appear when the state is selected, these IDs do not always exist.
This takes the display information from the master State and adjusts the coordinates and attributes of the various canvas objects comprising the state.
These create and delete the selection handles.
Methods for each relevant binding are defined on the image. For example, the arrowB1 method is invoked when button 1 is pressed with the arrow tool somewhere on one of the objects comprising the StateImage. This selects the current object if it is not selected, unselects everything else in the view if wasn't already selected, and remembers which object was clicked.
Mouse events pass the coordinates of the event and a canvas-object specific piece of data that identifies which canvas object of the StateImage received the event. For example, a click on any of the selection handles invokes arrowB1, but which selection handle was clicked is also passed to arrowB1.
Both specified at creation, these are used by the Trans constructor to attach itself to its source and destination states.
At the moment, this is an arbitrary one-line string, but when this information is used elsewhere, it will have to conform to some format. It is not clear whether this format should be enforced in this program, or in fact, how it should be enforced.
Much like the display information in the State class, this should probably be in a separate class to facilitate adding view types.
When this flag is set, every time the transition is informed that one its attached states is moved, all four control points and the position of the label are recomputed from the arc curvature number and the display information of the two states. The curvature number sets how much the spline deviates from a straight line connecting the centers of the two states:
Control of a transition's curvature: (a) c = -0.1 (b) c = 0 (c) c = 0.1 (d) c = 0.2
When this flag is cleared, the four control points and the position of the label are allowed to change independently.
When a transition's shape changes, all attached TransImages are informed. This list is updated and maintained analogously to the list in the State class.
Like the recreate defined on the State class, this returns a string that, when executed, recreates the transition. Only values differing from their defaults are written. One twist is that when the fixed curvature flag is set, the control points are not written, since they can be regenerated.
This calculates where the four control points and the position of the label should be based on the specified curvature value and the positions of the source and destination state. This is called by methods defined on the Trans class when the position of either state changes.
These are invoked by the State's move method, and either call snapArc or move the spline's control points. The control point on the source or destination state (hence the two flavors) is moved the given distance, the next nearest is moved 75% of the given distance, the next, 25% of the distance. This way, when both states move the same distance, all four control points are translated that distance, yet when only one state moves, both ends stay attached and the in-between points do something reasonable.
Like their State counterparts, they invoke a select or unselect method for an attached TransImage with the given view.
The View class contains
Each view displays a subtree of states, starting at the specified root and going down for a specific depth.
Converting from database to canvas coordinates involves subtracting the pan and then multiplying by the zoom.
These lists are maintained by the constructors and destructors of the StateImage and TransImage classes using the attach and detach methods defined on the View class.
This identifies which image created each canvas object. Since each image is composed of multiple canvas objects, this is essentially a grouping of canvas objects.
Each image is composed of multiple canvas objects, and this distinguishes them by function. For example, each selection handle on a particular image has a different type.
Each view has its own set of selected objects: State and Trans are selected, not their images.
Doing things this way is somewhat inconvenient: a selection request is passed to the view, which passes it to the object, which passes it to the image. But the view must pass itself to the object, since the object must determine which image will actually be selected.
The alternative is keep a list of selected images and whenever something is done to a list of selected objects, any change is eventually passed back to their masters.
This are invoked by the constructors and destructors of the StateImage and TransImage classes respectively. The one twist is that the detach methods also remove from the selection list the object being detached.
These are wrappers for the canvas create and delete method that update the mappings between canvas objects and objects (StateImages, TransImages).
These methods add and relocate a single selection handle. The select methods defined on the two image classes invoke addHandle.
These update the view-specific list of selected objects and invoke the select and unselect methods defined on the given object.
Given a bounding box and either enclosed or overlapping, this method returns a list of all the StateImages in or near the region. This is used when states are moved and their position in the hierarchy is changed, when new states are added, etc.
This destroys all the attached images and regenerates them, invoked when the depth or root state of the view changed.
The TransDialog class behaves similarly, except it edits transition-specific attributes.
Bindings
There are currently four tools: the arrow tool for moving, selecting,
and stretching things; the state tool for creating new states; the arc
tool for creating new transitions; and the text tool for editing the
text attached to states and transitions. Really, these tools are
different ways of interpreting key and mouse events.
New tools are defined using the newtool procedure:
newtool arrow @$bmd/arrow.xbm left_ptrThe arguments are the name of the tool, the bitmap to display on the tool palette, and the cursor to display in the canvas.
The mapping between my names for bindings and X's are defined with an array:
set bindings(B1)This gives the program a list of all possible bindings. The procedure called when an event occurs is defined in a two-step process:set bindings(B1Release)
bindproc {%v %x %y} {view x y} { set object [$view currentObject] if { $object != "" } { if { [ $object info method arrowB1motion \ -body ] != "" } { $object arrowB1motion \ $x $y [$view currentData] } } } bindto arrow B1Motion bindto arrow ShiftB1Motion bindto state B2MotionThis scheme allows the same procedure to be called for multiple events across multiple tools. In this example, the actual arguments of the bound procedure are the view and the coordinates where the event occurred (%v %x %y), the formal arguments are view, x, and y (variable names in the procedure), and the given procedure is bound to the arrow tool's B1Motion event, the arrow tool's ShiftB1Motion (mouse movement with both the first button and shift key held down), and the state tool's B2Motion event.
The procedure in this example first asks the View on which object (image) the event occurred. For example, if the user was dragging a handle on a StateImage, it would return that particular StateImage. Next, if there is an object there, it checks to see if there is a method arrowB1motion defined on the object. If so, it invokes the method, passing the coordinates of the event and the type (currentData) of the canvas object (e.g., NWHandle, spline, etc.).
Some actions are best done to the entire selected set, others are best done to a single object. Both schemes can be implemented in this framework. For example, dragging a state should move all selected states. When the method for arrowB1motion is given an event coming from the outline of a state, it invokes the move method on all the selected states explicitly. Dragging handle, on the other hand, only affect the state or transition whose handle it is.
Written by Stephen Edwards.