The goal of this project is to extend the Java-Smalltalk interface provided by ClassicBlend (by Applied Reasoning) to handle any custom Model/View/Controller application.
At present, ClassicBlend supports Smalltalk applications that have been built using the UIBuilder interface. Custom widgets can be added, but the process is long and complicated. Applications that include components which have not been defined as UIBuilder widgets are not supported at present.
We extend ClassicBlend so it can handle any Model/View/Controller application. This is achieved by defining ORB interactions for arbitrary Views on both the Smalltalk and Java sides. In addition to supporting widgets, the extended version of ClassicBlend supports basic geometric objects and wrappers.
1.1. ORB overview
The postscript document cbdoc.ps contains a detailed overview of ClassicBlend's framework for building widget proxies. It also discusses the structure and behavior of the underlaying ORB structure. This overview focuses mainly on the server since in general, the client side mirrors the Smalltalk implementation.
ClassicBlend's functionality if closely tied to that of the UIBuilder. CB has an ArcbBuilder class that has the same protocol as the UIBuilder, but instead of displaying visual objects on the screen, it sends messages over the ORB to the Java interface. This way, (almost) any application built using the UIBuilder can be executed with ClassicBlend.
The downside of this close coupling with the builder is that visual objects that have not been created by the UIBuilder cannot be linked to the Java interface using ClassicBlend.
In our extension to ClassicBlend, we implement additional functionality in the ORB, so that applications containing custom views can be executed with the Java interface. The process of extending ClassicBlend in this manner includes the following steps:
The above ensures that applications defining their own views would execute correctly under ClassicBlend.
3. Implementation
3.1. Smalltalk subsystem
In applications based on the Model/View/Controller interface framework, custom views are usually incorporated by following these steps:
The UIBuilder spec for an arbitrary view holder is the ArbitraryComponentSpec. In order to enable the ArcbBuilder to use this arbitrary view holder, the spec's dispatchTo:with: method had to be modified to incorporate double dispatching (available in all other widget specs). The modified dispatchTo:with: method sends the message arbitraryComponent:into: to the UILookPolicy to build the widget. This message is almost an exact copy of the old dispatchTo:with: method, with some messages changed to reflect the change of implementing classes.
The above changes enabled us to define the building method for an ArbitraryComponent in ArcbLookPolicy. The method arbitraryComponent:into: creates the proxy for this widget and registers it with the ArcbBuilder.
The ArcbArbitraryComponentProxy class is responsible for communications over the ORB. The most important implementation detail of the ArcbArbitraryComponentProxy is its update mechanism. When an application is running under ClassicBlend, no displayOn: messages are sent to its visual components (since the Java widgets define the way they should be displayed locally). Therefore, we need to send the displayOn: message to the view at the appropriate time. Since the view depends on its application model, we made the ArcbArbitraryComponentProxy depend on it as well. The proxy sends the displayOn: message to the view in the update: method of the ArcbArbitraryComponentProxy.
The proxy determines the view it corresponds to in the updateComponent method. The ArbitraryComponentSpec has an instance variable, component whose value is a symbol corresponding to the aspect name of the "View Holder" widget. By converting this symbol to a message name and sending it to the application model, we obtain a visual object which understands the displayOn: message. Here, we refer to that object as a "view", but it doesn't matter where in the VisualPart hierarchy its class actually is, as long as it understands the displayOn: aGraphicsContext message.
Every time the model changes, we send the modelChanged method to the Java proxy to prepare the view hierarchy for redisplaying. Then, the ArcbArbitraryComponentProxy instance sends the displayOn: message with itself as the parameter. Currently, the most frequently used portion of the GraphicsContext interface is supported by the proxy. For example, when the view sends a displayRectangle message to the graphics context (which is an instance of ArcbArbitraryComponentProxy when the application is running under ClassicBlend), the proxy sends the message displayRectangle over the ORB. The corresponding Java proxy, upon the receipt of this message, creates a rectangle view and adds it to the main view.
3.2. Java subsystem
The Java portion of our extension to ClassicBlend is briefly illustrated in the diagram below. The source code can be viewed by clicking on the class boxes.
Some source code details have been omitted from the above diagram. Access to all the source code is given in Section 4. Some design patterns occuring in our extension are discussed in Section 3.3.
We have optimized certain parts of the display process in order to avoid redundant displaying of graphical components. If each subcomponent is displayed immediately after it has been created in response to an ORB message from the server, all other subcomponents in the main view whose bounding boxes intersect that of the new one are also redisplayed. This leads to unnecessarily large number of view drawings, which in turn causes the whole view to flicker. To at least partially remedy this, we utilize a delayed display strategy, in which the ORB display message traffic is monitored, and if there have not been any display request for a certain time interval, the entire view (with all its subviews) is displayed. The monitoring object (ArcbArbitraryViewDraw) runs as a separate thread throughout the life of the main view.
Another thread of control was needed to correctly start the execution of the application (see (ArcbArbitraryViewUpdate). Its purpose is to ensure that the Java view hierarchy has been initialized before attempting to process ORB display requests.
3.3. Design patterns
The design of our extension to ClassicBlend contains some of the design patterns described in the Design Patterns book by Gamma et al.
ApplicationModel subclass plays the role of Subject, while the ArcbArbitraryComponentProxy plays the role of Observer.
GeometricView class hierarchy. The GeometricView class serves as the AbstractClass, containing the drawView(Graphic) abstract method as a primitive operation. The concrete subclasses of GeometricView (e.g. LineView) define the drawView(Graphics) method, which is called in the draw() method of their superclass.
ArcbArbitraryComponentProxy serves as the Strategy, the GraphicsView serves as the Strategy by declaring a common interface for drawing geometric views, and the concrete classes LineView, RectangleView, etc. serve as ConcreteStrategy classes. Our implementation differs from the Strategy pattern slightly. The ArcbArbitraryComponentProxy is not configured with one particular strategy; instead it creates new concrete strategies in response to ORB messages from the server.
At present, our extension to ClassicBlend implements only the fundamental graphics context functionality. In order to make our extension fully compatible with any VisualWorks view, the ArcbArbitraryComponentProxy must include the entire non-private interface of GraphicsContext. Most methods in the displaying protocol of GraphicsContext will result in server-client ORB messages when the application is executed under ClassicBlend. To incorporate additional GraphicsContext methods, follow these steps:
addMethodsToDefinition class method of ArcbArbitraryComponent.
orb messages protocol of ArcbArbitraryComponent that has the same name as the GraphicsContext method we are emulating (e.g. displayString:from:to:at:). Some GraphicsContext messages need not result in an ORB message to the Java display. Such methods usually modify the internal state of the GraphicsContext, without affecting the display immediately. For example, the translation method simply returns the current value of the instance variable translation. In general, accessing methods or methods which only affect the internal state of the GraphicsContext (and equivalently, ArcbArbitraryComponent) are defined in the spoofing protocol and need not be included in the addMethodsToDefinition class method.
ArcbClassicBlendProxy static method definitions, e.g., Def.define(new GraphicsContextDisplayStringAt(), "displayStringAt", 2);
com.arscorp.cb.methods.gc package (e.g. GraphicsContextDisplayStringAt).
ArcbClassicBlendProxy (e.g. displayStringAt).
GeometricView to handle the display of the new component (e.g. StringView). You may be able to utilize an existing class from the com.arscorp.cb.ifc.widgets.gc package.
make in the top-level ClassicBlend directory.
ArcbArbitraryComponent, modify the addMethodsToDefinition class method and the orb messages or spoofing instance protocols.
com.arscorp.cb.ifc.proxies, modify class ArcbClassicBlendProxy (add a static definition and a function to execute when the ORB message is received).
com.arscorp.cb.methods.gc, create a class for the new ORB message.
com.arscorp.cb.ifc.widgets.gc, subclass GeometricView (if needed).
Makefile to include the classes you created.
Please note that there may be exceptions to the above steps. However, in the general case, adding new methods is relatively simple. For debugging your Java code, use the Debug.println(String) method defined in class Debug. To turn debugging output on, change the value of the static variable debugging to true.
3.5. Controller issues
We didn't have time to implement propogation of events from the Java side to arbitrary controllers on the Smalltalk side. However, we did figure out what needs to be done to make controllers work properly without specific Java proxies. To understand the changes that need to be made, some background is needed.
3.5.1. How User-Interface Events Work in VisualWorks
There are two loops involved in VisualWorks user events: a polling loop that begins with a Controller and an event loop that begins with an InputState.
The polling loop is initiated by the Controller. It calls InputSensor>>pollForActivity, which with a WindowSensor will call InputState>>pollForActivity. Contrary to the name and initial appearance of the method, InputState>>pollForActivity actually waits on a semaphore for an event to change its state. It will only poll for a short time after state has been changed, to catch multiple state changes in quick succession. InputState>>pollForActivity will return when there is some new data to process.
When there is new data to process, the Controller asks the InputSensor (which passes the request on to the InputState) for mouse positions and other input device information. The Controller will decide whether or not to act on this new device information with Controller>>isControlActive. The Controller implements this function to return true if the pointer is in the corresponding View's display area and the blue button (right button) is not pressed. If isControlActive returns true, the Controller polling loop will call controlActivity and the rest is history.
Before proceeding, the reader should remember that there is one instance of InputState per image, one Sensor per DisplaySurface / Window, and many Controllers per Window.
The event loop is in InputState>>run (instance). It waits for an event to arrive by waiting on a semaphore that it got from its Screen instance. When the Screen has an event ready, it will signal the semaphore, and InputState>>run will retrieve the event using the primitive Screen>>primReadEvent. The Screen will parse the underlying OS/windowing system event structure and produce a VisualWorks event structure. The VisualWorks event structure is an 11-byte array; its definition is in InputState>>whatIsAnEvent (class).
Once InputState>>run has an event, it will will call process: with the event structure as a parameter. InputState>>process: will
DisplaySurface>>findRegistrant (class), and
The InputSensor child instance that is associated with the Window that
owns the event will receive one of the following event calls:
All of the calls will get the event structure as a parameter. IfeventKeyPress
eventKeyRelease
eventDoubleClick(InputState>>process:determines if a second click is a double click)
eventButtonPress(mouse down)
eventButtonRelease(mouse up)
eventEnter(mouse entered region)
eventUnknown
eventMouseMoved
eventExit(mouse left region)
eventDamage(repaint)
eventQuit(close the window)
eventCollapse(the window has been collapsed)
eventExpand(the window has been expanded)
eventWidgetColorChange(platform changed widget color preferences)
eventDestroy(undocumented)
InputState>>process: cannot find a window to receive the call, it will look for an EventDispatcher that is registered with it and pass the event on.
WindowSensor, the most common InputSensor child, will queue the window-related events for future action, and pause briefly for events the user would handle to let the polling loop get the changed data.
On the Java side, change ArcbArbitraryComponentView to handle events. Since ArcbArbitraryComponentView is a descendent of an IFC View, there are inherited methods that are called whenever a user input event can be caught by that View. Our implementation of these event handlers should package the incoming user event into a VisualWorks event structure, and pass the events on through the ORB to the additional InputState instance on the server side.
Add an additional WindowSensor child and InputState child for ArcbArbitraryComponent to use. The new InputState will have an new event loop. Instead of waiting for events from the Screen, it will receive messages from the ORB. It will have one registered function, receiveEvent: or some such, that will then call process: on the event. Everything else will follow from that, since every Sensor for "remote" windows gets its data from that one InputState instance on the VisualWorks side.
The issues that need to be dealt with before this can really be implemented are:
Components actually use the new WindowSensor and InputState. Otherwise, they will get events from the Smalltalk side, not the Java side. Likewise, one would need to make sure that no Windows that correspond to windows displayed on the Smalltalk side are binding to the new InputState instance.
In order to use the code supplied below, there must be a working ClassicBlend installation on your system. This source code has been tested with JDK 1.0.2, VisualWorks 2.5, and ClassicBlend 1.0 on Sun ULTRA-Sparc running Solaris 2.5.1. Both the Java and Smalltalk source files can be downloaded from the source/ directory. This directory also contains a sample Makefile for compiling the Java source. The Smalltalk code is available in both archived and unarchived formats. (Warning: filing in the Smalltalk code will make a minor modification to the UIBuilder, which will not change its functionality unless the UILookPolicy and ArbitraryComponentSpec have been previously changed from their original distribution.)
stsource.tar.gz archive (as with the Java code above). This will create a subdirectory stsource/ in the current directory. The stsource/ directory contains all Smalltalk source files.install.st.appletviewer html/demo/geometric.html and/or appletviewer html/demo/dice.html. If you wish to run these applications remotely, modify the html files to include the correct host name.javasource.tar.gz file to the top-level ClassicBlend directory. gunzip javasource.tar.gztar xvf javasource.tarjavasource.tar.gz file.make in the top-level ClassicBlend directory. Make sure that the CLASSPATH environment variable includes the top ClassicBlend directory.