On this page you will find class diagrams and some explantation of the design. The material is introductory, but should be sufficient to understand how to use the library, especially if you have used Fresco or InterViews. If you become completely stumped, you may want to read the Fresco tutorial. Much of Sgraphics was inspired by Fresco and InterViews. Just don't expect Sgraphics to be exactly like what you see in the tutorial. Sgraphics is much simpler, but hopefully simpler to use as well.
Note: Fresco is no longer supported by X-consortium or Fujitsu. So, please don't e-mail any of the people referenced in the tutorial with questions.
First lets start with the basics. What is a glyph? What kinds of glyphs are there?
Below is a class diagram of the basic geometry management and event handling classes. Lets go through each class one by one. Note: you can click on classes in the diagrams to get to the API documentation.
Class Diagram 1: Glyphs
The glyph is the basic graphical unit of Sgraphics. Just about every class inherits from the Glyph interface, everything from lines to buttons, and from event handlers to layout classes. Glyphs are used like small building blocks that can be connected an various ways. Most glyphs come in three flavors: GlyphImpl, MonoGlyph, and PolyGlyph.
GlyphImpl implements basic behavior for all glyphs.
MonoGlyph is based on a Decorator pattern. It has a body that is another glyph. When a client object makes a call to a MonoGlyph, it forwards the call to its body. Many times a MonoGlyph will do some special processing before and/or after it forwards the call to its body. You can think of a MonoGlyph as a wrapper around a body glyph that modifies the body in some way.
PolyGlyph is based on a Composite pattern. It is a container for other glyphs. Usually PolyGlyphs are used for building boxes that tile or align other glyphs.
The Viewer interface defines the methods necessary for event handling.
ViewerImpl implements Viewer and is the basic event handling class. A ViewerImpl placed in the glyph tree at any point, where it acts as an event dispatcher. To use a ViewerImpl you sub-class it and implement one or more of its event dispatching functions like mouseUp(...), mouseDown(...), etc. (Unless you use ViewerCallback).
ViewerCallback is an alternate approach. To make a callback, you implement the ViewerCallback interface and pass the implemented class to a ViewerImpl. Then, ViewerImpl will dispatch all events to the callback instead of making calls to its own methods.
Hint: Which method you use is a matter of style and preference. However, the callback method can be used to implement the State Pattern. ViewerImpl would correspond to the Context class, and ViewerCallback would correspond to the State class. Because ViewerCallback receives a reference to the calling ViewerImpl, it can replace itself to effect a state change.
SgraphicsAdapter is a class that connects a glyph tree to an AWT component. SgraphicsAdapter inherits from an AWT Panel and is insertable into any AWT Container. When an adapter is constructed it is usually passed a glyph that will be the top glyph in the tree, i.e. the root of the glyph tree. However, internally SgraphicsAdapter inserts a TopViewer object between itself and the root.
TopViewer is the master event controller for the system. When AWT asks SgraphicsAdapter to post an event, the adapter forwards the call to TopViewer. TopViewer then works in conjunction with all the ViewerImpl objects in the tree to dispatch events.
The strong separation of event handling/dispatching and geometry management increases cohesion in each system, and allows the possibility of designing custom event handling systems that can replace the existing one described here. It is possible to replace SgraphicsAdapter, Viewer, ViewerImpl, and TopViewer, effectively creating a new event handling system without disturbing the geometry management. (One caveat: widgets, and other classes that depend on ViewerImpl, must be replaced or reworked as well. Fortunately widgets are AWT classes with an extra interface for Sgraphics and they are easy to create.)
Class Diagram 2: Geometry Management
There are two parts to geometry management: finding the requirements of all the glyphs in a tree, and allocating space for them. Think of the process as a simple negotiation. Some object asks a root glyph what size it would like to be. The root glyph then asks its body (if it is a MonoGlyph) or its children (if it is a PolyGlyph) what size it/they would like to be. This process continues until the root can return its required size, called a requirement. Next, the object tells the root how much space it may have and where that space is on the display, called an allocation. The root then divides up that space for its body or children, which do the same until every glyph knows its size and where to draw itself. The final step occurs when the object asks the root to draw itself, beginning a recursive chain of calls to every glyph in the tree.
Actually, this conversation is a little more complicated because a requirement is really three requirements: natural, minimum, and maximum. Natural is the default size requirement. Minimum and maximum are the extremes a glyph expects the system to honor. Having these extremes allows proportional stretching and shrinking of glyphs.
Now, lets map these concepts onto the class diagram above. Requirements is an class that has 2 Requirement classes, one for each axis. Each requirement has a natural, minimum, and maximum size. (It also has an align requirement, but we will discuss that later.) Allocation is an class that has two Area classes, one for the allocation proper and one for clipping.
Now that you understand the responsibilities of the classes, it is time for an example.
Object Diagram 1: Geometry Management Example
In this example there are two objects that draw themselves: a rectangle and a line. The line has a margin wrapped around it to provide some space. The rectangle and the margin wrapped line are inside a hbox. A hbox tiles horizontally and aligns vertically.
Scenario 1: Geometry Management
The scenario is fairly straight forward. First preferredSize() is called on the adapter which calls requirements(). Then reshape is called which calls allocate(Allocation). Each call results in calls to every glyph in the tree. The hbox adds up requirements for its children: rect and margin+line. The hbox also divides up its allocation for the same children.
Event handling has two parts like geometry management. The first part consists of determining which viewers in the tree should handle an event. This is called picking. The second part consists of sending the viewer the event so that it can dispatch it to the correct method.
Scenario 2 is a scenario for Object Diagram 1 used to describe geometry management. Take a quick tour of the diagram and then read on about picking and dispatching.
Scenario 2: Picking and Dispatching
Picking works very much like requirements(). The Glyph interface defines the method pick(Picker). Picking begins by a call to pick on the root glyph. The root then determines if the event occurred within its allocation. This occurrence is called a hit. If the root is a geometry managing object the pick call forwards the call to its body its children for picking. As the pick call makes its way through the tree, each occurrence of a viewer (ViewerImpl) creates a new picker object and adds it to the end of the previous picker creating a chain. If some glyph within the viewer calls hit on the picker, pick preserves the chain, otherwise pick removes the picker before returning. When pick returns in root, the picker object will be the first link in a chain of pickers leading to the viewer that will dispatch the event.
A viewer will also call pick if any of its descendant glyphs or viewers are hit. This is because a viewer is opaque. The general idea is that a viewer has interest in handling events for any glyph within its allocation, including another viewer.
Dispatching the event follows a different course from all the examples above. Instead of using the glyph tree to do the job, the picker chain is used. The process begins with a call to processPick(Picker, GraphicsEvent) on the first picker. processPick(Picker, GraphicsEvent) forwards the call down the chain until a viewer wants to handle the event; a viewer that has a hit. At this point the viewer calls handleEvent(GraphicsEvent) to dispatch it. If handleEvent(GraphicsEvent) returns false, processPick(Picker, GraphicsEvent) will return false, and then the previous viewer in the chain (the one that called the current processPick(...)) will handle the event if it was hit. In this way, processPick(Picker, GraphicsEvent) walks the chain to a viewer, and handleEvent(GraphicsEvent) dispatches the event. But if the event is not handled, a viewer closer to the root will try to handle the event if that viewer was hit.
The actual dispatching of the event is simply a matter of handleEvent(GraphicsEvent) calling mouseUp(...), mouseDown(...), or one of the other methods in ViewerImpl. Or, optionally dispatching the calls to a ViewerCallback object.
Event handling is a little tricky, so an other example is in order.
Suppose a there is a simple glyph tree that contains a horizontal box with two squares, and pressing the left mouse button over a square changes its color. Try this on the applet below to see how the example works.
Object Diagram 2: Color Changer
The glyph structure is shown in Object Diagram 2. Each rectangle is wrapped in a changer object. The changer object, ColorChanger, is a viewer. See Class Diagram 3 below:
Class Diagram 3: Color Changer
Now look at Scenario 3 below. Notice how the right changer changes its color in handleEvent(GraphicsEvent) and then calls needRedraw() on itself. needRedraw() propagates up to the adapter where it can call draw(Painter). draw(Painter) then calls down the tree until the right rectangle is repainted.
Scenario 3: Color Changer
Sgraphics has a LayoutKit class which is a factory for making layout glyphs. It is used by calling its static factory methods that return objects of type Glyph. These glyphs are assembled in a tree along with figures and widgets to compose a graphical interface.
Two most common layout glyphs are vbox and hbox. A hbox tiles its children horizontally and aligns them vertically. The align attribute that is returned by a requirement() call is used for alignment. An alignment of 0.0 aligns along the top border of a hbox. An alignment of 1.0 aligns along the bottom border of an hbox.
For more information on what layout objects are available see LayoutKit. For a general introduction on how to use glyph based geometry management, see the Fresco tutorial, or Donald Knuth's book on TeX. The LayoutKit in Sgraphics was a port from Fresco, so much of what is in the tutorial is useful.
The FigureKit is similar to the LayoutKit. It is a factory for generating figures, like squares and circles. Methods return objects of type Glyph that can be placed in the glyph tree.
The widget package (this one is not a kit) consists of AWT Components that can be embedded in a glyph tree. Each widget sub-classes from the AWT Component and implements the Glyph interface. The only thing different about using a widget is how it is removed from its parent.
For comments or questions contact Mike Jones (Mike.Jones@mass.com)