Starting Out

Starting Out
Learning by Example
We provide a number of examples with GF/ST:
Demo Launcher - Used to launch the example applications. The launcher (actually, the class GFDemoLauncher) uses GFButtonGO’s to launch the applications.
About Dialog - The GFAboutDialog displays the propaganda information about the demo as well as the rotating tensegrity picture.
Drawing Editor - A kind of “catch all” application that lets you try out new GO’s you might create. You can access all the basic GO’s supplied with GF/ST from the floating palette. The Drawing Editor (actually the class GFDrawingView) shows the default behavior of all base GF/ST GO’s.
GFThreeDEditor - A good example of coupling underlying domain models (of 3-D figures in this case) with GO’s.
PsiVisualInspector - A new tool for your toolbox. We expect you’ll start using this inspector instead of the standard ones in Smalltalk, especially since you have them built into it, too.
These applications are all well-commented, and we expect you to browse through them to learn how they solve different user interface issued in GF/ST. We’ll be using them extensively in the “How To’s“ to refer to specific chunks of code.
For completeness, we’ll include a description of how to use each of the major demonstration applications. In them, we’ll explain the basics of what a GFHandle looks like, and so on. You may find it easier just to open them up and use them, and then refer back to the appropriate discussion in the manual when you need to. The exception to this approach is the Visual Inspector. There is a lot of functionality built into the Visual Inspector which you may discover by playing with it, but which might be easier to read about. For example, you can assign objects into instance variables using the connection handles, but it’s not obvious from looking at it. For details on using the Visual Inspector, refer to “Appendix A - The Visual Inspector“.
Following each “Using the...” section, we’ll provide a brief overview of how each of the applications works. These sections should give you a good road map for exploring the applications on your own, explaining the approach which was taken and any key design decisions made along the way.
Probably the most important learning-by-example tool we provide for people starting out is the Network Editor. It is a simple application, it’s easy to understand what it’s doing, and it illustrates all the basic concepts you need to know to get up and going. It forms the basis of an entire chapter: “GF/ST Application Design & Decisionmaking“.
Using the Demo Launcher
The Demo Launcher opens automatically when you install GF/ST. It shows four buttons from which you can launch the individual applications, and a Help button. We use the Demo Launcher in the runtime demonstration application as the user’s only entry point into the application, a typical Smalltalk application program approach. In runtime, we open the About Dialog first, and then the demo launcher, so if you close your Demo Launcher and then re-open it, you will be presented with the About Dialog before the launcher opens.
To open the Demo Launcher, evaluate:
GFDemoLauncher open.
It should look something like this, depending on what options you have installed:
The three buttons on the upper left open the Drawing Editor, the 3D Figures Demo and the Visual Inspector respectively. The next few buttons open additional demos that may or may not exist in your environement depending upon what you have loaded. The last two buttons opens the About Dialog and Help file for GF/ST, or for the runtime demo if you are in runtime. On-line Help is only available under Windows.
The on-line help files still reflect the original ownership of the products by Polymorphic Software. Please ignore references to Polymorphic and direct all inquiries to Instantiations.
The help files will be updated to reflect the new ownership of the products at a later date.
Understanding the Demo Launcher
The buttons in the Demo Launcher are instances of GFButtonGO. We created GFButtonGO because it was actually easier than dealing with the Visual Smalltalk interface to Windows buttons that show bitmaps, DrawnButton. If you use WindowBuilder Pro under Visual Smalltalk or VisualAge, or the UIPainter under VisualWorks, someone has already done the hard work for you. In GF/ST, we want to provide you with the low-level framework to build emulated widgets and to use host components. We use the GFButtonGO to demonstrate that emulated widgets are simple to make in GF/ST. You shouldn’t be able to tell any difference between GFButtonGO’s and standard Windows-style buttons. You can check the differences out for yourself using the Drawing Editor’s Host Components category of GO’s to place a GFHostWidgetGO button next to a GFButtonGO.
In Visual Smalltalk, the GFDemoLauncher was built as a subclass of ViewManager. In VisualWorks it is a subclass of ApplicationModel, and in IBM Smalltalk it is a subclass of GFApplicationWindowES (a subclass of WbApplication). They all follow a generic approach for opening a GF/ST application, which is described in detail for the Network Editor (see “Creating and Opening the Network Editor U“). The specific methods for the GFDemoLauncher are:
You can explore these methods yourself. The other methods in the class are for taking action when a button is clicked.
Using the Drawing Editor
This section discusses how to use the Drawing Editor. The Drawing Editor is a kind of “catch all” application that lets you try out new figures you might create. It also illustrates the default behavior of all GO’s supplied with GF/ST.
When we talk about the “Drawing Editor”, we’re mainly talking about the class GFDrawingView. Because the GFDrawingView is a very basic tool, we included it as part of the base GF/ST framework.
Visual Smalltalk
From the Launcher button in Visual Smalltalk, you’re using the GFDrawingViewWithStatus, a version that includes status panes at the bottom. Status panes are an “extra” in Visual Smallalk.
In VisualWorks, you’ll be using the GFDrawingEditor instead of the GFDrawingView. Use of the word “View” appended to a class name is standard for subclasses of ViewManager in Visual Smalltalk, but it implies the wrong thing to VisualWorks users. In this document, you will find us referring to GFDrawingView class, and if you’re a VisualWorks users, you’ll just have to bear with us and think: GFDrawingEditor.
You can open the Drawing Editor from the Demo Launcher, or by evaluating:
GFDrawingView new open. “In Visual Smalltalk”
GFDrawingView open “In VisualAge”
GFDrawingEditor open “In VisualWorks”
The platform-independent alternative, used from the GFDemoLauncher is:
GFDrawingInterface open.
The latter approach to opening the Drawing Editor emphasizes the role of the GFDrawingInterface in GF/ST. It glues together all the parts of a GF/ST direct manipulation application. The GFDrawingView is only used to coordinate the multiple windows of the Drawing Editor itself, the one showing the GFDrawing and the one displaying the palette, discussed next,
There is a slight delay the first time you open the Drawing Editor, as we cache the bitmap and other resources from files.
Basic Drawing Skills
Most things about the Drawing Editor should feel familiar to you.
There is a main window to draw-in, labeled Drawing, and a floating palette window. The items on the left of the palette are used to choose the category of tool you are using.
When you select an item on the left, the tools available in that category update. You always have a selection tool available at the top no matter what category you choose. Note that as you select categories and tools in the palette, the status pane at the bottom of the drawing window updates to let you know what’s going on.
The exact look of the Drawing Editor and the palette will vary slightly between Visual Smalltalk, VisualWorks, and VisualAge. For example, there is a multi-draw button on the palette in VisualWorks to be consistent with the UIPainter style. We have also included scaling and grid options in the menus.
The concept of a tool is central to GF/ST, as is the concept of handle. For an overview of the concepts, refer to “Basic Concepts and Classes“. For details of how you will be using tools and handles in your own application, you can start with the chapter on “GF/ST Application Design & Decisionmaking“. Right now, we’re trying to concentrate only on how the Drawing Editor behaves without getting hung up in too much detail.
Each tool may have a different behavior for drawing. For example, you draw rectangles by holding down the left mouse button and dragging. You draw polylines by clicking with the left mouse button at the endpoint locations of each line segment, and you double-click when you’re done. Try them out. Actually, this behavior is all taken care of by using different instances of the GFCreationTool.
When you’re done using the drawing tool you selected, the Drawing Editor automatically switches you back to the selection tool. The thing you created on the screen is a kind of GFGraphicObject; i.e., it is an instance of some subclass of the GFGraphicObject class.
Each graphic object on the screen can have its own specialized pop-up menu. You don’t have to select the object to bring up its menu. Just position the cursor over the object and click the right mouse button. Try some of the options to see how they work. Line graphical objects (or more generally, path figures) also have their own pop-up menus - you just have to aim well when you pop them up. Check out the different behavior of the various types of graphic objects. Where do these menus come from? They are either inherited, or you can simply use the standard getMenu event to get the menu from anyone you want. See the discussion in How To’s sections “Setting the Menu of a GO“ and “Setting the Menu of a Drawing“ for details of using and disabling menus in GF/ST.
Each graphic object can have its own specialized set of handles. To be technically accurate, graphic objects do not know what handles they have - they simply trigger the event #generateHandles, and any object can return the appropriate handles. The Drawing Editor demonstrates the default handles which are provided when the event is not handled.
Handles are typically drawn as little black boxes in an appropriate location. When you select a handle, the graphic object takes some appropriate action, such as allowing you to move a corner when you select a corner handle. Handles can do pretty much anything you want. For example, the default handles on the GFRoundedRectangleGO look like this:
Note that when you select a handle, the status pane updates to show you what the handle is used for. You can use the descriptions we provide, or you can put in your own. The descriptions and the coupling of the events to status pane display makes a useful and unobtrusive “hint” tool for your end users.
You can use the corner handles to resize a GO. When you select the border width handle, the pixel width of the border is adjusted as you move the mouse vertically. The corner radius handle adjusts the sharpness of the rounded corners. You can connect two graphic objects using the connection handle. Connections are a particularly difficult aspect to deal with generally in a graphical application. When you connect two objects, you’ll find that the line connecting them is another graphical object that is constrained to move with the objects it connects - a GFDependentLineGO. The connecting line has its own pop-up menu, letting you change it from a line to an arrow, or to an orthogonal path. Any, all, or none of these handles may be useful to your application, so we provide powerful ways to choose, display, and otherwise manipulate handles. See the discussion in “Specializing Handles in the Network Editor“ and the “How To’s” for more details.
Moving a Graphic Object
To move a graphic object, select inside of it but not over a handle. Hold the left mouse button down and drag to any position. The Drawing Editor automatically scrolls when you move outside of the currently visible area.
Multiple Selections, Grouping, and Composites
You can select more than one graphic object by holding down the shift key as you select them. Alternatively, you can “rubber-band“ around multiple objects when are using the selection tool. Once you have more than one object selected, you can create a group or a composite using the “group” or “compose” menu options. Grouped objects can only be manipulated as a whole, while composite objects are positionable within the composition area on the screen, and can be moved as a whole. Try them out. It’s easier to see the difference when you play with them.
Groups and composites are one of the most powerful tools in GF/ST, since they let you compose arbitrarily complex graphic objects from very basic ones. See “Creating a GO in the Network Editor“ for a discussion of using groups and for guidance in how to decide between groups and composites.
Front-Back Ordering
You will find out as you move objects that they are implicitly aware of their position front-to-back relative to one another. You can change their ordering using, of course, an ordering tool. We provide two position tools - one to move a figure to the top, and one to move it to the back. They are both instances of GFGOActionTool. The tool holds onto an action block that is evaluated for the selected GO’s in a view. You should feel free to get more creative.
Here’s a fun thing to try. Create five or size graphic objects like rectangles. Make them different colors using the fill-color handle or the pop-up menu. Overlap them slightly, top to bottom as shown below:
Select every other one top to bottom using the shift key as you select the individual objects. Now, letting go of the shift key, hold the left mouse button down while it is positioned somewhere inside of a selected figure and move the group you’ve selected. You do not have to group them to move them all together, just have multiples selected. The front-to-back ordering is maintained as you move them, and you’ll see the selected figures hide behind the ones you did not select as you move the group.
The undo operation in GF/ST is very powerful. We use a “memento“ approach, and it is generalized for use in any of your applications, with or without GF/ST. Give it a test by undoing up to fifteen things you’ve done. We chose to limit the size of the bounded stack tracking undo-able actions to fifteen in the Drawing Editor, but you can set it to be unlimited or to any size you want. See the comments in GFBoundedStack for more information on the undo stack itself.
Undo is difficult to achieve in Smalltalk without violating encapsulation - how do you undo an operation on some object or an operation done by an object on itself, without having access to protocol or state you should not know about? The approach in GF/ST uses the “memento” pattern discussed in “Mementos“ in “Miscellaneous Topics“ of “Visual SmalltalkWarning: If you do not use finalization under Visual Smalltalk 3.0.x, you must explicitly release your GO’s that use bitmaps (GFImageGO, GFGroupGO, GFButtonGO, and any of the Visual Inspector object GO’s). In addition, you should either use an unbounded stack for undo’s (the default is 15) or turn the option off via noUndo. The reason is that the GFBoundedStack holds onto instances of GFMemento in order to undo deletion operations you might perform. If undo is enabled, you should not release GO’s when you delete them, because an undo might cause it to come back. And, since you can’t release it when you delete one, if the stack is bounded, it is possible that the GO will be garbage collected when the stack grows, without your having released it. If you use finalization, this problem goes away. If you do not use finalization for some reason, you may want to consider making an unbounded stack or “no undo” be the default.Mostly GFGraphicObject-Related How To’s
Warning: If you do not use finalization under Visual Smalltalk 3.0.x, you must explicitly release your GO’s that use bitmaps (GFImageGO, GFGroupGO, GFButtonGO, and any of the Visual Inspector object GO’s). In addition, you should either use an unbounded stack for undo’s (the default is 15) or turn the option off via noUndo. The reason is that the GFBoundedStack holds onto instances of GFMemento in order to undo deletion operations you might perform. If undo is enabled, you should not release GO’s when you delete them, because an undo might cause it to come back. And, since you can’t release it when you delete one, if the stack is bounded, it is possible that the GO will be garbage collected when the stack grows, without your having released it. If you use finalization, this problem goes away. If you do not use finalization for some reason, you may want to consider making an unbounded stack or “no undo” be the default.
Mostly GFGraphicObject-Related How To’s “.Check out the GFMemento class and the GFDrawingView, where it is used, for details. The comments in the GFMemento class provide a discussion of how the memento pattern has been implemented in Smalltalk. You should find the GFMemento class to be useful in non-GF/ST applications as well.
Host Components
We’ve thrown in a few host components, or widgets, for fun. All host components are instances of GFHostWidgetGO. A GFHostWidgetGO can hold onto any kind of host component. We’ve included host components in the Drawing Editor for Button, List, Text, and EntryField. We included our own text figure in the host components category even though it is not a host component like static text would normally be.
True host components always show up on the top of a drawing because the underlying window system doesn’t understand how to deal with the non-host components like rounded rectangles. They also don’t display quite as smoothly when you manipulate them as the other components because the host windowing system that is drawing them isn’t as good at it as we are. Specifically, we use a double-buffering technique in GF/ST, as discussed in the “GFDrawingPane“ section of “Basic Concepts and Classes“.
Under Visual Smalltalk and VisualAge, the GF/ST host components are real Windows components. Under VisualWorks, they are true VisualWorks widgets. In the Drawing Editor, we’ve elected to make them active as soon as you unselect them, or select something else on the screen. In other words, if you draw a button on the screen, click on something else, and then click back on the button, it behaves just like a real button, since it is a real button. The consequence of this choice is that you need to use rubber-banding to re-select a host component once you’ve drawn it. If you were developing an application for building user interfaces, you would probably want to be able to switch between a building mode and a test mode to avoid confusion.
See any flashing as you manipulate graphical objects? We’ve spent a lot of time optimizing display performance. Also see the discussion in the “GFDrawingPane“ section of “Basic Concepts and Classes“ for more details.
Notice major any degradation of performance as you add tons of graphic objects? We use a GFQuadTreeElement approach to be able to handle large numbers of objects. Most drawing programs use outlines as you manipulate objects on the screen. The problem with outlines is that you cannot really see how your object will look when you get done modifying it. In addition, the performance of many drawing packages will degrade rapidly as complexity increases. The reason for this degredation is apparent when you consider what goes on as you drag an object across a complicated display of other objects, maintaining knowledge of z-ordering. Even if your application ignores z-ordering, you still need to determine what objects on the screen are affected by your changes to another object. Conceptually, it is not hard to do, but in practice, simple approaches make dragging, selecting, and scrolling become your biggest performance bottleneck. GF/ST’s quad-tree approach manages the spatial complexity to maximize performance. See the discussion in “Quad Trees“ in “Miscellaneous Topics“ of “Visual SmalltalkWarning: If you do not use finalization under Visual Smalltalk 3.0.x, you must explicitly release your GO’s that use bitmaps (GFImageGO, GFGroupGO, GFButtonGO, and any of the Visual Inspector object GO’s). In addition, you should either use an unbounded stack for undo’s (the default is 15) or turn the option off via noUndo. The reason is that the GFBoundedStack holds onto instances of GFMemento in order to undo deletion operations you might perform. If undo is enabled, you should not release GO’s when you delete them, because an undo might cause it to come back. And, since you can’t release it when you delete one, if the stack is bounded, it is possible that the GO will be garbage collected when the stack grows, without your having released it. If you use finalization, this problem goes away. If you do not use finalization for some reason, you may want to consider making an unbounded stack or “no undo” be the default.Mostly GFGraphicObject-Related How To’s
Warning: If you do not use finalization under Visual Smalltalk 3.0.x, you must explicitly release your GO’s that use bitmaps (GFImageGO, GFGroupGO, GFButtonGO, and any of the Visual Inspector object GO’s). In addition, you should either use an unbounded stack for undo’s (the default is 15) or turn the option off via noUndo. The reason is that the GFBoundedStack holds onto instances of GFMemento in order to undo deletion operations you might perform. If undo is enabled, you should not release GO’s when you delete them, because an undo might cause it to come back. And, since you can’t release it when you delete one, if the stack is bounded, it is possible that the GO will be garbage collected when the stack grows, without your having released it. If you use finalization, this problem goes away. If you do not use finalization for some reason, you may want to consider making an unbounded stack or “no undo” be the default.
Mostly GFGraphicObject-Related How To’s “ for additional discussion.
Understanding the Drawing Editor
Now that you’ve looked at the Drawing Editor, let’s take a closer look at how GF/ST works under the covers. We’ll divide our discussion between the topics of:
By looking through the class GFDrawingView, you can gain some appreciation of what we mean by our earlier statement that the Drawing Editor simply shows the default behavior of graphic objects. The GFDrawingView class does almost nothing. It is comparable in complexity to the GFFloatingPaletteWindow you use to select tools from the various categories. All the work is done by the underlying GF/ST framework classes you were introduced to in “Basic Concepts and Classes“.
GF/ST provides clear paths of communication between:
Communication may be required between any and all of these components. For example, the GFAboutDialog shows only communication between domain information (the position of the 3-D figure in 2-D coordinates) and what is seen on the screen - there is no user interaction. The Drawing Editor is an example of an application that does not have any identifiable domain model information; e.g., a rectangle in the drawing is not displaying the state of some other interesting object. Everything that happens in the Drawing Editor is held within the drawing that is created.
You do not need to (and are hereby actively discouraged from doing so) maintain direct references between the objects taking part in this interesting dance, since we use an event model to communicate between them. See the discussion in “What You Absolutely Must Know to Use GF/ST“ for background on the event model and a contrast with the Model-View-Controller approach. We believe that GF/ST takes the good parts of both MVC (loose coupling and factoring) and a modern event-driven approaches.
Handle Concepts
The information that is being created as you manipulate the graphical objects in the Drawing Editor can be directed anywhere and dealt with in any manner that is appropriate to your application. All direct interaction with the graphical objects is done through a handle (an instance of GFHandle or one of its subclasses). In the Drawing Editor, the handles (GFTrackHandles actually, which appear by default as black squares) send the information directly to the graphical object which responds accordingly.
Select a rectangle in the Drawing Editor. The handles on the four corners transport information about the position of the corners of the rectangle. When you drag one of these handles, it sends the growBy: message to the graphical object. When you hold down the left mouse button in a graphical object and you are not on a handle, a GFSelectionTrackHandle is created dynamically that transports information about the changes in position to the graphical object.
You can see that handles provide the central interface for input from the user in a GF/ST application. They take input from the user and route it to the appropriate object. Since handles play such a key role, you’ll need a powerful way to define and control what handles show up, what they do, and what they look like. Refer to the discussion in “Specializing Handles in the Network Editor“ for detailed discussion of various handle protocols, as well as the “How To’s”. The 3-D Figures demonstration shows some even more interesting variations on handles which we will be discussing in that section.
Tool Concepts
You are always using a tool of some kind in the Drawing Editor. When you are selecting objects, you are using the GFSelectionTool. When you are creating a figure, you are using a GFCreationTool of some kind. In the Drawing Editor, you select a tool from the palette. In another application, the state of the application may dictate the tool which is used. For example, the GF/ST Launcher only uses a tool that allows figures to be selected, but which disables the drag-select (rubber-banding) that is available in the Drawing Editor.
We expect tools to be the part of GF/ST which will be the least-modified by users. Tools are very important to GF/ST applications, but the ones supplied with it should solve most general problems. When you find you need some specialized behavior for a tool, though, you should just have to make slight adjustments to the ones we supply.
An aside for those readers interested in some historical design perspective. Traditional Model-View-Controller architecture depends on a triad of the domain model, and a view-controller pair. Visual Smalltalk modifies the MVC paradigm slightly. The NotificationManager provides the event dispatching function of the controller, pushing the remaining responsibility of the now-defunct controller to the view. Under IBM Smalltalk and VisualAge, the same effect is accomplished using the OSEventHandler class. The result is that in a pure MVC-based approach, there is one controller for every view, while in Visual Smalltalk and VisualAge, there is one controller in the entire system. In GF/ST, the tool takes the place of the controller, providing a one-to-many relationship between the currently active controller (tool) and the views (figures). This approach provides tremendous flexibility over the event stream that the figure ultimately sees.
Using the 3-D Figures Demo
The 3-D Figures demo is not designed to be a general 3-D drawing editor. It was born from a desire to illustrate ways to couple together domain model classes with a graphical display. We’ll be discussing just what that means later on. For now, let’s just stick to what the demo does.
You can open the 3-D Figures demo from the launcher or by evaluating:
GFThreeDEditor open.
There are only three options available in the demo, and they are presented to you as three buttons at the top of the screen. The buttons themselves are host components, (actually, instances of GFHostWidgetGO) and we’ve disabled your ability to move and otherwise manipulate the buttons. The kinds of GO’s the buttons produce are shown below each button. Note that on some platforms, we’ve added in buttons to toggle between “Animate” and “Stop” and to “Clear” GO’s from the screen.
When you select a GO, it shows a central handle that you can use to rotate it, or more specifically, modify the pitch and yaw of the object. The X, Y, and Z labels are actually handles, too. They allow you to adjust the X, Y, and Z size of the object when you hold down the left mouse button over the handle and drag.
Understanding the 3D Figures Demo
You may already have existing classes that are used in your application. To use GF/ST, you will need to understand how to hook these classes into the graphical display tools in the GF/ST framework. The 3-D Figures demo is a good example of how to do just that.
More About Handles
Add and select a cube in the 3-D Figures Demo. The displayed cube actually has four handles. One is the black square in the center that looks like the standard handles generated for any GO. The other three handles are the letters X, Y, and Z. When you use any handle in this demo, the handle sends messages to a separate instance of an interface class, which then manipulates an instance of a cube. In this scenario, the cube is considered to be the domain model - a cube knows its dimensions and what its orientation is. The graphical display is a reflection of the state of the cube - which way it is facing, how tall it is, and so on. The instance of an interface class could be ViewManager in VisualSmalltalk, ApplicationModel in VisualWorks, and in VisualAge, it is a subclass of WbApplication. It could simply be a subclass of Object, as long as it can fulfill its role of coordinating the flow of information from the handles to the cube itself.
In the GFThreeDEditor, we use a GFParameterizedTrackHandle. We use the #generateHandles event (see “Adding a GO for a Network Node“), and when the figure needs handles, we create them on-the-fly. For example, from the GFThreeDEditor>>handlesForFigureIndex: method, here is what the “Z” handle looks like:
on: aGO
at: #topLeft
change: (Message
receiver: self selector: #changeZ:delta:)
with: i)
scrollFlag: false;
limit: false;
displayImage: (GFTextGO text: ‘Z’)
When you grab the “Z” handle and move it, the handle sends the message changeZ: delta: to the GFThreeDEditor. The first argument is the index of the GO tracked by the GFThreeDEditor, i, and the second argument is a point that is the change in mouse position. In its changeZ:delta: method, the GFThreeDEditor then tells the underlying domain model to change its Z dimension.
Another interesting point about the handles in the 3-D Figures demo is that the letters used for the handles are created using GF/ST. In fact, you can make a handle out of any GF/ST graphical object. The X, Y, and Z handles are made of GFTextGO objects.
Composites and Grouping
The 3-D Figures demo shows the power of being able to group graphic objects into composites. For example, each cube you create in the demo is made up of the six closed polylines (one for each face) contained within a composite. This approach makes it easy to treat the displayed cube both as a whole and as a cluster of component parts. For example, when the cube is moved, it is treated as a whole. The z-ordering of the 3-D figures relative to one another also requires that the figures be treated as a whole. On the other hand, z-ordering of the faces as you rotate the figure must be determined within a figure. Menu selections can also be applied to the specific polyline you clicked on. Try bringing up a menu on one of the sides of the cube and make the filled polyline transparent or change its line width. Composites let you retain the control over the independent GO’s, while a group would only let you treat them as a whole.
Dependent Lines
Add a tensegrity to the 3-D Figures demo. The red, blue, and green lines are actually dependent lines that are automatically updated when the polylines that define the figure are updated. These colored lines are the same connecting lines that can be created from the center handle of a rectangle while in the Drawing Editor.
From the GFThreeDEditor>>addPsiSolidAt: method, the dependent lines are created as follows:
lines at: 1 put: (GFDependentLineGO
startLocation: (GFLocator
on: (gos last at: 1) at: #point: with: 1)
stopLocation: (GFLocator
on: (gos last at: 8) at: #point: with: 1)).
(lines at: 1) color: Color red.
The lines variable is a collection of the GFDependentLineGO’s that is created at the beginning of the method and added to the GFCompositeGO at the end. The instance variable gos is a collection of collections, one for each of the GFCompositeGO’s in the drawing. For efficiency of display and manipulation of 3-D objects in a 2-D world, each of the secondary collections tracks the individual GO’s in the composite as well as the composite itself. (It’s just a caching trick to eke some more speed out of the demo, since determining face order in a rotating 3-D object is computationally intensive).
Note the use of GFLocator’s to define the position of the start and stop location of the dependent line. See the discussion in “GFLocator“ of “Basic Concepts and Classes“ for an overview of how locators work.
What’s a tensegrity? Besides being our logo and the name of a damn good persistent object framework for Smalltalk, a tensegrity is a term coined by R. Buckminster Fuller to describe a structure that is held together by tensile, rather than compressive forces. The simple tensegrity you’re looking at is an octahedron composed of three rods (represented in the figure as the thick red, green, and blue lines) held in place by the tension from the connecting lines.
More about Menus
Notice that the pop-up menu for a cube is different than the menu for a tensegrity. Actually, the Faces submenu is different, because we want the faces of the tensegrity to remain transparent. You can specialize menus very easily because we use the event system to get the menu for an individual graphic object. The application itself can define the menu to be used for each graphic object if the default menu is not wanted. The application is also able to define what, if any, menu should be used for the overall drawing, when you pop-up a menu outside of a figure.
In the GFThreeDEditor, each 3-D figure on the screen is a GFCompositeGO. The composite is made up of filled polylines (GFPolylineGO’s), one for each face of the figure, and each of the polylines has its own menu. In the GFThreeDEditor>>addFigure: method, you can see how the menu for the polyline (referred to as aGO) is hooked into the GFThreeDEditor itself:
aGO when: #getMenu
send: #menuForFigureIndex:
to: self
with: (Array with: aGO with: (gos size + 1)).
When you click on the right mouse button while the mouse is positioned over a face of the 3-D figure, GF/ST triggers the #getMenu event. The GFThreeDEditor is in turn sent the message #menuForFigureIndex:, the argument for which is an Array with two elements (the first being the polyline GO, and the 2nd being the composite). The menuForFigureIndex: then returns the appropriate application-specific menu.
Understanding the GFAboutDialog
The GFAboutDialog is shown whenever you open the Demo Launcher. You can invoke it again from the launcher, or by evaluating:
GFAboutDialog open.
The GFAboutDialog>>open method shows one way to couple the GFOwnDCDrawingPane with the GFDrawingInterface in a window that also has standard subpanes. Refer to that method directly, and see the discussion in “Creating the GFNetworkNode GOCreating and Opening the Network Editor U“ for detailed discussion of similar issues in the Network Editor example.
The spinning tensegrity in the GFAboutDialog is not an animation using a pre-defined set of bitmaps. The animation is accomplished by sending messages (in a background process) to an underlying domain model object to change its rotation, asking it for the equivalent 2-D coordinates in its new position, and asking the composite figure to redraw itself. The background process is initiated in the GFAboutDialog>>startSpin method.
Understanding the Visual Inspector
You can invoke the Visual Inspector from the GF/ST launcher. The Visual Inspector is a tool that shows the state of objects and the relationships between objects graphically. It is a complex enough tool to warrant a separate chapter in this document (see “Appendix A - The Visual Inspector“). Non-source versions of the Visual Inspector are supplied as a freeware on some platforms, which is another reason that we’ve broken out the how-to-use-it documentation from the how-to-understand-it documentation.
Visual Inspector classes are prefixed with Psi, reflecting its heritage as a Polymorphic Software utility, not as part of the base GF/ST framework or product.
You can open the Visual Inspector from the launcher, or by evaluating:
PsiVisualInspector open.
To open a Visual Inspector on an object, send it the message psiVisualInspect. For example:
Transcript psiVisualInspect.
The Approach
The Visual Inspector stands in contrast to the 3-D Figures demo in the way it uses the GF/ST framework. The 3-D Figures made use of existing GF/ST classes, like GFPolylineGO, to display underlying domain model classes, like GFCube. The Visual Inspector was built by adding new subclasses to GFGraphicObject that are specialized for inspecting Smalltalk objects.
Creating the Visual Inspector was an easy task using the GF/ST framework. Almost all the coding fell into two categories: displaying the desired representation of the object in question, and providing the behavior for manipulating the fields of the object. Everything else, from the display, handle behavior, and constraints imposed on referencing lines, came from the framework provided in GF/ST.
New Classes
We subclassed off of GFCachedGO to create PsiAbstractObjectGO. Rather than draw a potentially complex series of figures for every object, we build the bitmap representation of the figure in a single method. This gives the inspector its terrific speed in display and manipulation. (It also means that a scaled view in the Visual Inspector uses scaled bitmaps, which do not look as nice as they could if they were redrawn at the new scale each time.) Among other things, PsiAbstractObjectGO holds the common display behavior for the figures in the inspector, giving them that chiseled look we liked.
To flesh out the hierarchy, we initially created three subclasses of PsiAbstractObjectGO - PsiObjectGO, PsiVariableObjectGO (a subclass of PsiObjectGO), and PsiVariableByteObjectGO. These classes were needed to provide different behavior for the three flavors of objects available in Smalltalk. To deal with primitive objects, such as nil, Integer, true, false, and Symbol, we also created a subclass of GFTextGO, PsiPrimitiveObjectGO. To round it all off, we added two more classes under PsiObjectGO - PsiOrderedCollectionGO, and PsiDictionaryGO. Since the base image development environment provides these specialized inspectors, we decided their usefulness would extend into the Visual Inspector as well.
In addition to the PsiObjectGO hierarchy, we needed an object to represent the reference held by one object to another object. We created a subclass of GFHandle, called PsiObjectReferenceHandle, to take care of this task. The added responsibility of this class is to keep track of its connection, a dependent line of some sort.
The Visual Inspector Put in Context
Besides being a useful tool for your development, the Visual Inspector is different than the other example applications provided with GF/ST, since it extends the framework. It adds in new GO subclasses and a new handle class. We encourage you to extend the GF/ST framework, and we caution you that, often, your problem can be solved by combining existing GO’s and making use of the events and parameterization that are already present. Refer to “GF/ST Application Design & Decisionmaking“ for more discussion and help in making these kinds of decisions in your own application.