BUILD GEM DIALOG BOXES
Stepper Motor for ST
Up to now, the Antic ST Resource has published programs that got necessary user-supplied information either directly from the keyboard or from alert boxes. While alert boxes are fine for small amounts of communication, such as "pick one of three choices," they fall short when more detail is needed. The keyboard allows for more detail, but isn't as friendly the GEM interface standard.
People are comfortable working with paper "forms" they can fill out. In GEM, the equivalant of a paper form is called a dialog box. Unlike an alert box, a dialog box can accept multiple lines of text, or detect when the user clicks on graphic buttons or switches inside the box. Examples of dialog boxes include the File Selector Box and the box that pops up when you "show info" on a disk or file.
This month, we're going to create and manipulate our own dialog box. Although the program is written in DRI's Alcyon C from the Atari ST Developers Kit, the same principles apply for users of other ST languages- such as OSS Personal Pascal, TDI Modula-2 and Prospero Pro Fortran-77.
Since we're using Alcyon C, we'll create it using one of the three Resource Construction Sets (RCS) available. Resources are collections of text strings, rectangle descriptions and pictures which are kept separate from individual programs. Thus anyone can come along later and, for example, change the program's English text into French without disturbing the rest of the program.
Two of the RCS programs are from DRI itself. The original RCS was supplied with the Atari ST Developers Kit, and RCS 2.1 can be downloaded from the CompuServe SIG *Atari Developers Forum. The third is the RCS supplied with MegaMax C. All three RCS programs are compatible with one another. For example, this means you could create a resource file with the DRI RCS and edit the same file with the MegaMax RCS.
Those of you with OSS Personal Pascal can easily create these resource files from within a running program. The C packages, however, make this feature too cumbersome for demonstration here
Anybody who programs the ST for awhile finds out that GEM loves rectangles. A GEM dialog box is always a rectangle-whether visible or invisible-and holds smaller rectangles inside it, which may in turn contain even smaller rectangles. This is explained as a "parent/child" relationship, where the smaller child rectangle is always completely contained within the larger parent rectangle and may not overlap.
This arrangement of nested rectangles is also known as a "tree of objects". The information structure within each tree allows GEM to track the size, color, location and other specifications of each rectangle. The "root" of the tree points to each of the parent root's children, which themselves point to any children they contain, etc. Visualize this relationship as spreading branches of a tree.
Above: Diagram of ST Stepper Motor dialog box (drawn with DEGAS) showing the GEM names and attributes. Below: Completed onscreen dialog box.
For the demonstration program this month, we'll create a dialog box- resembling a control panel-to operate the hardware described in this issue's 8-bit Stepper Motor Robot-Controller article. The GEM dialog box performs the same functions as the keyboard-operated program for the 8-bit Ataris. (You'll need to read the 8-bit article to understand the overall stepper motor project.)
Antic Disk owners can LinkLine the program over to their STs right away. LOAD both STEPPER.PRG and STEPPER.RSC to your ST. For the program to fully work, you need to build the interface board described in the 8-bit stepper motor story. However, if you just want to study the programming of ST dialog boxes, this program will run only if you have your printer connected and online.
If you don't have this month's Antic Disk, carefully type in Listing 1 STEPPER.C, and SAVE a copy to disk. Compile and assemble this C source code down into a .o file, and then link it to apstart, with aesbind, vdibind, osbind, and libf trailing along behind. Relmod the resulting .68K file into a .prg file and you're half finished. We now need to create our resource file.
BUILDING A RESOURCE
Figure 1 is a picture (drawn with DEGAS) of the dialog box created for the ST Stepper Motor program. Using your Resource Construction Set to reproduce the rectangle sizes exactly as shown is not important. The important thing is that you correctly name each of the objects and correctly set their individual attributes. For example, let's look closely at the four objects at the upper right of the dialog box-these objects combine to form a controller for the way we change and monitor the top location of the camera platform.
There are three smaller boxes below a full-width box that contains the word "Top" inside it. Of the lower three boxes, the center one has a four-digit number in it and the two flanking boxes contain a down and an up arrow. All four object boxes are of type BoxText, and are shadowed and centered. The two arrow objects are also touchexits. The main difference between them is their names.
The "Top" box has no name, because it's only there as a title. The up arrow box is named TOPUP, the down arrow is named TOPDN and the number is named TOPNUM. Each object has its own unique number, so we can use these names just like declared constants when we later need to determine which object we are dealing with. When you're finished creating your new resource, save it to disk. The Resource Construction Set will automatically create a .h header file for inclusion with your C program, containing all the names for the parents and children, along with their object numbers.
For example, because the up arrow object is a touchexit, when the program runs and we interact with the dialog box we will receive the number of the up arrow object- TOPUP-whenever the user presses the left mouse button while pointing at that object. We can test for the number of the object which caused the exit from the dialog box and act accordingly. In this case, getting the object number TOPUP from the dialog box directs us to increment the TOP number by one.
SAVING A RESOURCE
Finish creating your new dialog box using your RCS and the example in Figure 1. Save it to disk using the name STEPPER. When you examine the directory for that disk, you'll find the RCS has created at least three files:
1. .RSC file-the resource itself.
2. .H file-contains the names and object numbers. This file is for C programs only, but the RCS from Digital Research can also create files compatible with Pascal or Fortran-77.
3. .DEF file-tracks how different trees in a single resource are classified. Categories are dialog boxes, menus, free strings, or unknown. This file is only needed when you reopen a resource file for editing. You can edit a resource without the .DEF file, but you must reclassify all trees inside the resource file itself. Incidentally, RCS 2.1 creates a .DFN file, which works the same way.
When the resource is created, you can run STEPPER.PRG by just double-clicking on its icon. The program will search for, and load, the STEPPER.RSC file, presenting it for interaction. Let's examine the program in detail.
This program is written in the original Alcyon C contained in the Developers kit. It is not the new Version 4.14. We'll switch when we learn that enough readers have access to Version 4.14. Those of you with MegaMax C should find little problem in converting this program-basically just substitute sprintf() for the published program's ftoa(). Lattice C and Mark Williams C owners are also encouraged to try.
The top few lines contain remarks for the title and version date. Right below, we #Include the stepper.h file constructed by the RCS and the stock osblnd.h that everybody knows and loves.
Next come #defines, where we redefine some funny C operators into understandable words. The last three lines construct LWGET(x), which functions like an inline PEEK statement for words, and OB_W / OB_ H, which will allow us to get the width and height of object rectangles when we update them.
Below the #defines are definitions for OBJECT and TEDINFO structures. Structures are a convention in C to relate different sizes of data into an easily accessed form. They are similar to Pascal RECORDS.
Below the structure definitions come the global variable declarations including the 16-bit hits, the 8-bit chars, and the 32-bit longs.
Now we start our program logic. In main(), which every C program must have, we describe the program's operation briefly. Here, we Initlalize() the application, then move_the_ stepper_motor(), and repeat this action while( not finished );. When we finally get finished-when finished sets TRUE-we'll termlnate() this application and return to the desktop.
Inltialization here hooks our program into GEM and gets our application "handle." Then it opens a virtual workstation that returns a world of in-formation about the type of terminal we're on, in the array workout.
The first of two new calls, rsrc__ load() when given the path/filename of the needed resource file, will determine how big the resource is, reserve enough memory, and then load the resource into the just-reserved memory section. The second call, rsrc_gaddr(), will return the long address of the memory location holding the root of the desired tree.
From here we can search the tree for any desired object within. Before we leave the initialization function, we set a, b, c, and d - which match the function and value of a, b, c, and d in the 8-bit Atari program-to their proper port bit-pattern values. Then we force the mouse pointer sprite back to a pointing arrow, and set finished to FALSE, because we haven't finished the program yet.
MOVING THE MOTOR
In move__the__stepper_motor(), we have the major block of code for interacting with our dialog box. The forms library call: form_center() will figure out the screen position to center the dialog box (based on the size of the dialog) and return them to the variables xdial, ydial, wdial, and hdial. The next call, find_box_ sizes(), computes the clipping rectangle needed for each numeric display inside our dialog.
form__dial(0...) saves for later redrawing a copy of the screen portion covered up by the dialog box. form_dial(l...) draws the "growing box" display from size x, y, w, h to size xdial, ydial, wdial, hdial.
We draw our dialog box on the screen with the objc_draw() call, which needs to be told the location of the ROOT of the tree to be drawn (box_address), which object to draw (TREEl), how many children deep to draw (2), and the largest clipping rectangle expected, (x, y, w, and hdial).
We now begin interaction with the displayed dialog. The form_do()
call needs the ROOT of the tree to interact with, and the number of the
editable text field (TEDINFO) on which to place the cursor when the dialog
box is first displayed. We have no editable text in our box, so we pass
in a zero. GEM will control and monitor all interaction with the displayed
dialog box until the user clicks on one of the designated "exit" objects.
GEM then returns the object's number to the program, which in our case
gets placed into the variable called button.
should find little
problem in converting
this program from
When GEM passes control back to our program, we use the next 10 lines to test for exactly which object the user clicked on. Using our TOPUP button, the third line would detect when it was pressed for an exit and divert control to the function called d_topup(). The do loop construct will continue to activate the dialog box until the pressed exit button is the quit button.
form_dia1(3...) forces a redraw of the screen that was covered by the dialog box. When we fall out of the loop, the next line is form__ dial(2...), which draws the "shrinking box" graphic onscreen, to show the utility closing. The screen is redrawn from the image which was saved in the form_dial(0...) call earlier. Now that we want to leave the application, we set finished to TRUE, and return to the outermost loop, which will terminate() and exit.
The next function is find_box_ sizes(), which uses an AES object library call named objc_offset() to discover the x,y coordinates of the upper left corner of the object you ask it for. In our case, for each number box we ask for, (TOPNUM, BOTNUM, CURRNUM, or SPEEDNUM), we also get the width and height of the box so we may use the described rectangle as a clipping window. If we did not, then every time we updated each number, GEM would redraw the entire dialog box, at a cost of 1-2 seconds
UP AND DOWN
The next eight functions are called each time their function button is pressed. For example, d_topup() is called whenever the TOPUP button is pressed. The routine d_topup() simply increments the variable top and then calls adjust(), passing in the tree, object, string, and clipping rectangle wanted. All eight routines work the same, except for d_curup() and d_curdn(), which also have the responsibility of placing the control bytes out the PRINTER port.
The next function, adjust(), takes the value of the variable passed to it, changes the numeric value into an ASCII text string, then tells the dialog box where to find the just-created string of ASCII digits when it is needed for display.
The function ftoa() changes a numeric value into the ASCII string. MegaMax C users will want to substitute sprintf() for this call. Next, set_text() tells the dialog box where the newly created string is located, and objc_draw() redraws the number box object. Before we leave, beep() sounds a click from the speaker.
Right below is the set_text() function, for which I thank ANTIC ONLINE columnist Tim Oren for his help. This function needs the address of the ROOT of the dialog's tree, the number of the OBJECT you desire to change, and the address of the first byte of a null-terminated string of digits to show.
To begin, we declare obj_specification to be a pointer (*) to a TEDINFO structure. We then assign a value to this pointer by computing and retrieving the ob_spec value, which in this case points to the TEDINFO structure itself. Using this pointer to a structure, we assign to the structure entries te_ptext and te__txtlen the values for the address of the new string and the length of the new string.
The next two functions, move_ top() and move_bottom(), describe how to perform timed, programmed moves of the camera stage to the top of available travel, or to the bottom. We set up a for loop to cover the range of travel desired, and then call d_curup() or d_curdn() as desired. Next to last, beep() takes a LO, HI, and loop length value and uses these to perform simple tones through the monitor speaker.
Finally, terminate() does just that; the only difference from every other terminate() call we've published so far is inclusion of a rsrc__free() call, which will disengage and free the memory we reserved for the resource file when we first started the program.