CW CHAPTER 4
Jump to navigation
Jump to search
CHAPTER 4: OBJECT TREES AND DIALOG BOXES
----------------------------------------
[NOTE: Words enclosed in asterisks (i.e. *word*) should be
read as italicized text. This text file is copyright 1993 by
Clayton Walnum. All rights reserved.]
We now know how to handle the two simplest of GEM's forms,
the alert box and the file selector. So, it's time to move
on to the granddaddy form of them all: the dialog box.
Because the dialog box is so versatile, we could discuss its
uses endlessly and still not exhaust its possibilities. For
that reason, this chapter's discussion should not be
considered as a complete guide to dialog boxes, but only as
an introduction. Once you understand the way dialogs work,
the only limit will be your imagination.
--The Program--
In the CHAP4 folder of your *ST Assembly Language Workshop*
disk are the files PROG4.S, PROG4.PRG, SAMPLE.RSC, SAMPLE.H
and SAMPLE.DEF. The first two are the source code and the
executable file for this chapter's sample program. The
PROG4.PRG file is ready to run, but if you'd like to
assemble the program yourself, follow the instructions that
came with your assembler or see this book's Appendix A. The
file SAMPLE.RSC is the resource file for this chapter's
program. It must be in the same directory as PROG4.PRG when
you run the program. Finally, SAMPLE.H and SAMPLE.DEF are
the header file and definition file for the sample dialog.
When you run this chapter's program (make sure the
SAMPLE.RSC file is on the disk!), you'll see the dialog box
shown in Figure 4.1. Clicking on the OK or CANCEL buttons
will exit the dialog. Clicking on the up or down arrows will
change the value displayed in the NUMBERS object. Clicking
any of the other buttons brings up an alert box containing
the name of the object selected. Notice that, with the radio
buttons, only one may be selected at a time, while the
OPTION 1 and OPTION 2 buttons can be on or off in any
combination.
[INSERT FIGURE 4.1.]
You can enter your name and age (lie if you want to) in
the text fields. Use the arrow keys on your keyboard to move
between the two fields (or click on them with the mouse),
since, due to the OK button being set up as a default,
pressing Return will exit the dialog box. Try to enter
something other than upper- or lowercase letters in the name
field, or something other than a number in the age field. No
dice, right?
--The Definitions--
Before we get into a detailed discussion of dialog boxes, we
must first define a couple of terms: objects and trees.
Objects are used to visually represent each item that
makes up a dialog box. You've seen them hundreds of times:
boxes and buttons and text strings. Each object has its own
set of attributes that tailor it to the programmer's (and
eventually, the user's) needs. From a programming point of
view, an object is a data structure, the members of which
describe the object, storing all the necessary information
to bring that object up on the screen.
The objects of a dialog box are connected in an object
tree. A tree is a way to link items in a hierarchical
manner. That is, there's one main item (the tree's root),
which has connected to it other items, which, relative to
the tree's root are called children, and relative to each
other are called siblings. The children may also have
children of their own (and thus become parents), and so on
down the line, each new group of siblings subordinate to the
ones that have gone before.
An object tree is an array of objects, the attributes
of which are stored in the previously mentioned data
structure. Three elements of an object's data structure
determine the way the object fits in with the rest of the
tree. Specifically, each object contains, among other
things, a pointer to the next sibling, a pointer to the
first child (the head) and a pointer to the last child (the
tail). Because of this complexity, few programmers bother to
try and design dialog boxes from scratch. They instead use a
resource construction program.
--An RCP Mini Tutorial--
When we created a menu bar in chapter 3, we used Megamax's
Resource Construction Program (RCP) to build our resource.
Now, we'll use it again to create this chapter's sample
dialog box. If you're using the Atari Developer's Kit, don't
fret; the Resource Construction Set (RCS) that came with
your kit will work equally well for our purposes. The only
difference is the operation of the programs. (Of course, the
complete resource file is on your *ST Assembly Language
Workshop* disk. You don't have to create it on your own, but
this is good practice if you're not familiar with resource
construction programs.)
So, everybody load up their resource construction
programs, and let's get busy. Figure 4.1 is the dialog box
we'll be building. You should refer to this illustration as
you construct your version.
Once you have your resource construction program
loaded, go to the File option of the menu bar and select
New. A window titled NONAME will appear. To the left of this
window are the types of resources we can build, represented
in icon form. Place the mouse pointer over the dialog icon,
press and hold down the left button, and drag the icon into
the window. Release the button, and a dialog box will
appear, asking you for the name of the new tree. Clear the
NAME field by pressing Escape. Then type SAMPLE and press
Return.
Now, double-click the new dialog icon (the one you
dragged to the window). This will open the dialog,
presenting you with a "blank slate." The icons to the left
will change to a dialog box parts kit. The parts shown are
icon representations of the types of objects you can use to
build your dialog box. The types are as follows:
Button................A box containing centered text
String................................A line of text
FText.................................Formatted text
FBoxText.............A box containing formatted text
IBox........................An invisible graphic box
Box....................................A graphic box
Text....................................Graphic text
BoxChar...A graphic box enclosing a single character
BoxText.................A graphic box enclosing text
Icon..........................Description of an icon
Now let's start filling our dialog box with objects.
Step 1: Drag the STRING object onto your dialog box,
and then double-click it. A dialog box containing a number
of attributes will appear. At the bottom will be a line
labeled TEXT. Clear the line by pressing the escape key.
Then enter (without the quotes) "THIS IS A SAMPLE DIALOG
BOX," and press Return. Drag your new string to the top of
the dialog box and center it, as shown in Figure 4.1.
Step 2: Drag an ICON object onto your dialog box, and
double-click it. Click the EDIT ICON button from the dialog
box that appears, and draw the ANALOG icon (or any icon you
like) with your mouse. When complete, click the OK button.
Drag the icon to the left of the text created in Step 1 and
position it as shown in Figure 4.1.
Step 3: If the icon is not shown in inverse (selected),
give it a single mouse click. When the icon is selected,
type Control-C (copy), point the mouse to the right-hand
side of your dialog box, and type Control-V (paste) twice
(the first keystroke deselected the original icon). You
should now have a duplicate of the first icon. Drag it into
position, to the right of the string, as shown in Figure
4.1.
Step 4: Drag the BOX object (the empty rectangle) onto
your dialog box. Place the mouse cursor on the lower-right
corner of the box and, holding down the left button, stretch
the box until it's about the same size as the box labeled
RADIO BUTTONS in Figure 4.1. Position the box, and double-
click it. An attribute dialog box will appear. Use the mouse
to select the SHADOWED attribute. Then click the OK button.
Step 5: Using the same method as in Step 1, create a
string that reads "RADIO BUTTONS," and position it at the
top of the box created in Step 4.
Step 6: Drag a BUTTON object into the box created in
Step 4, and double-click it. When the attribute dialog
appears, select the following options: SELECTABLE, RADIO
BUTN, and TOUCHEXIT. (Note that sometimes an attribute--in
this case, SELECTABLE--has already been activated for you.)
Modify the text field to read "#1." Then click the OK
button. While the radio button is still highlighted (shown
in inverse), type Control-N, and name the object "RADIO1."
Position this button in the upper left of the "Radio Button"
box, beneath the string, as shown in Figure 4.1.
Step 7: Use the copy and paste functions (as in Step 2)
to place another radio button to the right of the one
created in Step 6. Change the button's text to read "#2" and
change the object's name to "RADIO2."
Step 8: Using the method in Step 7, create four buttons
labeled "#3," "#4," "#5," and "#6," and name them "RADIO3,"
"RADIO4," "RADIO5," and "RADIO6," respectively. See Figure
4.1 for placement.
Step 9: Drag the EDIT:______ (not the one surrounded by
a box) object into your dialog box, and double click it. If
it isn't already selected, turn on the EDITABLE option.
Clear the PTMPLT field with the escape key, and then type
"NAME:" followed by one space and 10 underline characters.
Use the down arrow on your keyboard to move the text cursor
to the PVALID field. Then press Escape to clear the field.
Type "aaaaaaaaaa." Use the down arrow key to move the text
cursor to the PTEXT field. Then backspace till you reach a
tilde (~) character. Now type "@" followed by nine spaces.
Click the OK button. Then name the object "NAME." Position
the object as shown in Figure 4.1.
Step 10: Drag a second EDIT:______ object into your
dialog box, and double-click it. Make sure the EDITABLE
option is set. Change the PTMPLT field to "AGE:" followed by
one space and two underlines. Change the PVALID field to
"99." Move to the PTEXT field and backspace until you reach
a tilde character. Then type "@" followed by one space.
Click the OK button, name the object "AGE," and then
position it as shown in Figure 4.1.
Step 11: Drag another BUTTON object into your dialog
box, and double-click it. Select the attributes SELECTABLE,
SHADOWED and TOUCHEXIT. Change the button's text to "OPTION
1." Click the OK button, name the object "OPTION1," and
position it as shown in Figure 4.1.
Step 12: Use the copy and paste functions to create a
duplicate of the button created in Step 11. Change the
button's text to "OPTION 2," name the object "OPTION2," and
position it as shown in Figure 4.1.
Step 13: Drag the BOXTEXT object into your dialog box,
and double-click it. Select the SHADOWED attribute. Clear
the PTMPLT and PVALID fields (if necessary). Then change the
PTEXT field to four spaces followed by four 0s and four more
spaces. Using the method shown in Step 4, stretch the box
one segment higher (as you pull down on the mouse, the box
will automatically "snap" to the next size). Name the object
"NUMBERS," and position it as shown in Figure 4.1.
Step 14: Drag another BOXTEXT icon onto your dialog
box, and double-click it. Set the TOUCHEXIT option. Make
sure the PTMPLT, PVALID and PTEXT fields are clear. Then,
when positioned on the PTEXT field, hit space, Control-A,
space (the keys, not the words). Resize the object as in
Step 13, and name it "UPARROW." Position it on top of the
box created in Step 13 as shown in Figure 4.1.
Step 15: Use the copy and paste functions to create a
duplicate of the UPARROW object. Then clear the PTEXT field
and press space, Control-B, space. Name the object
"DWNARROW." Then position it on top of the NUMBERS object as
shown in Figure 4.1.
Step 16: Drag another BUTTON object into your dialog
box, and double-click it. Set the SELECTABLE, DEFAULT and
TOUCHEXIT options. Change the text field to "OK." Resize and
position the object as shown in Figure 4.1. Then name it
"OK."
Step 17: Drag yet another button into your dialog box,
and double-click it. Set the SELECTABLE and TOUCHEXIT
options. Then change the TEXT field to "CANCEL." Resize and
position the object as shown in Figure 4.1. Then name it
"CANCEL."
And that's it. You've just created your first dialog
box. Now, to save all your hard work, close the dialog box
by clicking on the upper-left corner of the window. Then
select the SAVE AS option from the FILE menu. Name the file
"SAMPLE," and you're on your way. To leave the RCP, select
the Quit option from the File menu.
You should now have three files on your disk: SAMPLE.H,
SAMPLE.DEF and SAMPLE.RSC. These are the files that the RCP
created. SAMPLE.H contains all your object and tree names as
a series of C #defines. If you want to refer to the objects
by name in your program, you must *include* this file in
your source code. Unfortunately, because the file is in C
format, you must first change it to assembly language form,
by changing each #define line to an *equ* line.
The SAMPLE.DEF file contains information the RCP uses
for its own purposes, and the SAMPLE.RSC file is the tree
data for our dialog box. We'll load this data into memory
when we run our program.
--Object Attributes--
That was a fast course in the use of a resource construction
program. You probably have many unanswered questions. For
example, what do all those attributes do?
SELECTABLE simply means that the user can select the
object. When the object is selected, it will be displayed in
inverse video. If you set the DEFAULT option when editing an
object, the object will be selectable with the Return key,
as well as with a mouse click. Obviously, only one object at
a time can be set as a default.
The EXIT and TOUCHEXIT attributes are similar: they
both cause the dialog box to be exited when selected. The
difference is that, with TOUCHEXIT, the mouse button need
not be released to exit the dialog box.
What did you think about the RADIO BUTN option? Radio
buttons are handy devices, allowing the programmer to set up
a series of related buttons, only one of which may be
selected at a time. As soon as a button is selected, the
previously selected button is turned off. They get their
name from those old car radio tuners with the push buttons
to select the channel. An important note: In order for radio
buttons to operate properly, they must have the same parent
object; that is, they all must be "on top of" the same
object.
The CHECKED, SHADOWED, OUTLINED, CROSSED and DISABLED
options affect the way the objects will be graphically
represented on the screen. You can easily see their effect
by using your RCP to set them for various objects. The
options' names describe their effect.
An EDITABLE object may be modified in some manner by
the user.
--Editable Text--
Now, what's the story behind those strange text fields
PTMPLT, PVALID, and PTEXT? These three strings combine in
such a way as to tell GEM which part of the text is editable
and what characters the user is allowed to input.
PTMPLT is used as an input mask. Any text entered here
will be displayed on the screen and will be unchangeable
(except underline characters) by the user. PTMPLT also tells
GEM where the user can edit the text. We indicate this with
underline characters. In Step 9 above, the unalterable text
is "NAME:" and the editable area, where the user will enter
his or her name, is represented by the 10 underlines.
The PVALID field tells GEM what type of characters we
want the input restricted to. Each underline character in
the PTMPLT field must have an entry in the PVALID field as
follows:
Code Characters Allowed
---- ------------------
9 0 to 9
A A to Z, space
a A to Z, a to z, space
N 0 to 9, A to Z, space
n 0 to 9, A to Z, a to z, space
F DOS filename characters, plus ? * :
P DOS filename characters, plus \ ? * :
p DOS filename characters, plus \ :
X Any character
In Step 9 we entered 10 lowercase A's in the PVALID
field, limiting the user's input to upper- and lowercase
letters. A logical choice for a person's name.
Finally, the PTEXT string will be combined with PTMPLT
when the latter is printed. Unlike the text in PTMPLT, the
string stored in PTEXT is editable. This is handy when you
want an editable text field displayed with a default
setting. For example, in Step 9, if we had made the PTEXT
string "FRED," when the dialog box appeared on the screen,
the text cursor would appear to the right of the string
"FRED." The user could then just leave the string as it is,
and thus select FRED as his name, or he could backspace over
it (or use the escape key to clear it) and type in something
new. When the user exits the dialog box, the new information
will be found in PTEXT, replacing what we had stored there
previously.
When we set up our editable text objects in Steps 9 and
10, however, we wanted to end up with the text cursor to the
left of a blank field, ready for the user's input. To do
this, we must either enter an "@" or a null as the first
character of the PTEXT string. To reserve space for any text
the user may enter, we must fill the rest of the PTEXT
string with blanks (actually, any character will work; once
GEM sees the "@: or null, it'll ignore the rest of the
string and go on its merry way).
--A Look at an Object's Structure--
Now that we've created our dialog box and played with it a
little, it's time to dig into the actual structure of
objects.
We said before that an object is a data structure, the
members of which describe the object, storing all the
necessary information to bring that object up on the screen.
When we load a resource file into memory, each object is
allocated memory as follows:
word ob_next
word ob_head
word ob_tail
word ob_type
word ob_flags
word ob_state
longword ob_spec
word ob_x
word ob_y
word ob_w
word ob_h
Here, *ob_next* is the index of the object's next sibling,
*ob_head* is the index of the object's first child, and
*ob_tail* is the index of the object's last child. (Remember
that the objects are stored in an array of structures. The
indices mentioned above are the location of the object
within the array.)
The *ob_type* field is the object type and will contain
one of the following values:
Object Type Value
----------- -----
Box....................20
Text...................21
BoxText................22
Image..................23
ProgDef................24
IBox...................25
Button.................26
BoxChar................27
String.................28
FText..................29
FBoxText...............30
Icon...................31
Title..................32
The *ob_flags* field contains the object flags and will
be one (or a combination of more than one) of the values
shown below:
NONE...............0x0000
SELECTABLE.........0x0001
DEFAULT............0x0002
EXIT...............0x0004
EDITABLE...........0x0008
RBUTTON............0x0010
LASTOB.............0x0020
TOUCHEXIT..........0x0040
HIDETREE...........0x0080
INDIRECT...........0x0100
You should recognize most of these from your work with the
RCP.
The *ob_state* field holds the current state of the
object as follows:
NORMAL.............0x0000
SELECTED...........0x0001
CROSSED............0x0002
CHECKED............0x0004
DISABLED...........0x0008
OUTLINED...........0x0010
SHADOWED...........0x0020
You've seen most of these before, right?
The *ob_spec* field contains object-specific
information and changes depending on the type of object
that's being described. The possible values of this field
are as below:
Object Type Contents of *ob_spec*
----------- ----------------------
Box.............Object's color and thickness
Text............Pointer to TEDINFO structure
BoxText.........Pointer to TEDINFO structure
Image...........Pointer to BITBLK structure
ProgDef.........Pointer to APPLBLK structure
IBox............Border's color and thickness
Button..........Pointer to text string
BoxChar.........Object's color and thickness,
and the character to display
String..........Pointer to text string
FText...........Pointer to TEDINFO structure
FBoxText........Pointer to TEDINFO structure
Icon............Pointer to ICONBLK structure
Title...........Pointer to text string
Notice that the value stored in *ob_spec* can be a pointer
to another structure containing additional information about
the object. We'll take a look at one of the structures,
TEDINFO, shortly.
Finally, *ob_x*, *ob_y*, *ob_w*, and *ob_h* contain the
object's coordinates, width and height, respectively.
--The Mysterious TEDINFO--
The second structure type we need to look is called TEDINFO.
Whenever our object has an editable text field, we need to
store information about it in a TEDINFO structure (actually,
when using an RCP, we don't have to worry about storing
information in the structure; it's done for us). Here's what
a TEDINFO structure looks like in memory:
longword te_ptext
longword te_ptmplt
longword te_pvalid
word te_font
word te_junk1
word te_just
word te_color
word te_junk2
word te_thickness
word te_txtlen
word te_tmplen
Here, *te_ptext* is a pointer to the PTEXT string,
*te_ptmplt* is a pointer to the PTMPLT string, *te_pvalid*
is a pointer to the PVALID string, *te_font* is the text
font (3=system font; 5=small font), *te_just* is the
justification (0=left; 1=right; 2=centered), *te_color* is
the color and pattern type, *te_thickness* is the thickness
in pixels of the border (0=no border; 1 to 128=thickness
inward from the edge; -1 to -127=thickness outward from the
edge), *te_txtlen* is the length of the string pointed to by
*te_ptext*, and *te_tmplen* is the length of the string
pointed to by *te_ptmplt*. You can ignore *te_junk2*; it's
reserved for future use.
Don't let all this technical stuff get you down. In
most cases, when using the RCP to put together your dialog
box, you won't have to worry about the contents of the above
structures. But, in case you want to do something more
sophisticated, you do need to understand where to find
information about your dialog box. An example of this is the
NUMBERS object in the sample dialog. In order to get the up
and down arrows to change the value shown, we have to be
able to get at the displayed strings. This is just one
example of the creative ways you can use a dialog box.
--The Workings--
Now, just to make absolutely sure your head is spinning,
let's look at the source code for this chapter's program, to
see how everything we've discussed ties in with the dialog
box.
At the top of the listing, we include the file
SAMPLE.H. This file contains the name of the object tree
that represents our dialog box, as well as the names of all
the objects within the tree. Each object is given a number.
This number is the index used to find the object within the
array. (A tree is an array of objects, remember?) Listing 2
at the end of this chapter shows what the SAMPLE.H file
contains.
If you created your dialog box from scratch, you may
find that your objects are numbered differently than those
in Listing 2. Don't worry about it. That just means we
constructed our dialog boxes a little differently. For
instance, the example's object numbers don't run in perfect
order. Some numbers are missing because, in the course of
constructing the dialog, I removed a couple of objects from
the screen without actually deleting them from the tree.
Those objects were assigned numbers, but since they remain
unnamed (and unused), they don't appear in the .H file.
(They are, however, still taking up space in the resource
file.)
The equates after the *include* in Listing 1 assign to
logical names the values of various parameters we'll be
using when handling the dialog.
Now, look at the data section of the program. The
variable *num* will hold the current value of the dialog's
number control. Following that is our AES parameter block.
Notice something strange? The first element of this array,
which should be the address of the control array, is 0. This
is because, starting with this program, we're going to be
using a shortcut for initializing the control array for each
function call.
If you look below the *apb*, you'll see how we're going
to do this. Each AES function now has its own control array,
already initialized with the proper values. All we have to
do to set up our *apb* for an AES call is place the address
of the function's control array into the first element of
the *apb* array. You'll see how this is done a little later,
when we discuss the main program.
Following the control arrays is the filename for our
resource file. Then, the byte array *number_str* is the
string we'll use to change the displayed value of the
NUMBERS object, in response to the user's clicking one of
the arrows.
Finally, in the *bss* section, we have several
variables and buffers, as well as our GEM arrays and our
stack.
--The Main Program--
The main program starts off by performing all the usual
initializations: calculating the size of the program area,
releasing unused memory, setting the global array, and
calling *appl_init*, *rsrc_load*, and *rsrc_gaddr*. The only
thing different here is the way we're initializing the
control array for each AES function call. Rather than five
lines that initialize each control value individually, we're
just moving the address of the already initialized arrays
into the first element of the *apb*, like this:
move.l #appl_init,apb
This method saves us a lot of extra work and shortens our
source code considerably.
The process of loading a resource file and finding its
address is the same for any resource, whether it be a dialog
box, a menu, or something else. If you need a refresher on
the *rsrc_load* and *rsrc_gaddr* functions, please refer to
chapter 3. In this chapter, we'll start our discussion with
how to display a dialog box.
--Displaying a Dialog Box--
In order to display a dialog box, allow the user to use it,
and close the dialog, we perform the following seven steps:
1) Center the dialog's coordinates.
2) Reserve screen space for the dialog.
3) Draw an expanding rectangle.
4) Draw the dialog.
5) Activate the dialog.
6) Draw a shrinking rectangle.
7) Erase the dialog.
A few of these steps are optional. For example,
although a dialog box usually looks best in the center of
the screen, there's no reason you have to center it. Also,
you can skip the expanding and shrinking rectangles, if you
like. This will slightly speed up the process of displaying
and removing the dialog, at the expense of losing some of
the visual effects.
Although some steps are optional, we will, of course,
look at them all.
--Dialog Display Step 1--
The first thing we do in the program is modify the
coordinates of our dialog box so that it'll appear in the
center of the screen. This is done with a call to AES
function #54, *form_center*, like this:
move.l #form_center,apb
move.l dialog_addr,addr_in
bsr aes
move int_out+2,dial_x
move int_out+4,dial_y
move int_out+6,dial_w
move int_out+8,dial_h
First, we place the address of the *form_center*
control array into the first element of the *apb*. Then, we
place the address of our dialog box into the first element
of the addr_in array, after which we call the AES. After the
call, *int_out* will contain the dialog's new coordinates.
We save these coordinates for later use, by moving them into
*dial_x*, *dial_y*, *dial_w*, and *dial_h*. These are the
dialog's x coordinate, y coordinate, width, and height,
respectively.
--Finding an Object's Coordinates--
Because we'll be doing some work by hand, as it were, on the
NUMBERS object in order to update the number it displays, we
must find its eventual position on the screen. We do this
with a call to AES function #44, *obj_offset*, like this:
move.l #objc_offset,apb
move #NUMBERS,int_in
move.l dialog_addr,addr_in
bsr aes
move int_out+2,num_x
move int_out+4,num_y
In the first line, we move the address of the
*objc_offset* control array into the first element of our
*apb*. Then, we place the number of the object for which we
want the coordinates into the first element of *int_in*.
Finally, we place the dialog's address in *addr_in* and call
the AES. After the call, *int_out0* will contain an error
code: 0 if an error occurred or a value greater than 0 if
there was no error. Also, *int_out1* and *int_out2* will
contain the X and Y coordinates of the object. In our
program, we copy these values into the variables *num_x* and
*num_y*.
--Dealing with TEDINFO--
After we have these values, we need to tell the dialog to
use our *number_str* string, instead of the one it's
currently using. To do this, we must access the NUMBERS
object's TEDINFO structure. Here are the lines of code that
do this, the first of which is a macro call:
tedinfo_str dialog_addr,NUMBERS
move.l #number_str,(a5)
The main body of the macro looks like this:
move.l \1,a5
add.l #(\2*24)+12,a5
move.l (a5),a5
To trick the object into using our string, we have to find
the pointer that points to the string contained in the
NUMBERS object. Remember that our tree, which is now pointed
to by *tree_addr*, is an array of structures, each structure
describing one of the objects within the tree. Just like any
other array, we can access a particular element by using an
index. The object whose structure we wish to locate is
NUMBERS, and, thanks to our handy RCP, NUMBERS has been
defined in the SAMPLE.H header file to the value of the
index we need.
The *ob_spec* member of the structure that describes
NUMBERS, contains a pointer to the TEDINFO structure that
holds the pointer to our string. So, in the first line of
the macro, we load the address of our resource into A5. Now,
we need to locate the *ob_spec* field of the numbers object.
If you look at an object's structure as shown previously,
you'll see that each object is 24 bytes long. So, because
NUMBERS is the index of the object in the tree, NUMBERS*24
gives us the first byte of the NUMBERS object.
Another look at an object's structure will tell you
that the *ob_spec* field is 12 bytes from the start of the
object. So, by adding 12 to NUMBERS*24, we get the location
of NUMBERS's *ob_spec* field from the beginning of the
entire resource tree.
The entire formula, then, is (NUMBERS*24)+12. By adding
the result of this formula to the address we stored in A5,
we'll have the address of NUMBER's *ob_spec* field in A5.
That's what we do in the second line of the macro, as shown
above. The third macro line above uses address register
indirect addressing to load the contents of the address
pointed to by A5 into A5. This address is the address of the
tedinfo's string, because that address happens to be stored
in the first four bytes of the tedinfo.
--Dialog Display Step 2--
Now, on to step 2, reserving space on-screen for the dialog.
We have to do this so that, when we remove the dialog from
the screen, GEM will be able to restore the display. A call
to AES function #51, *form_dial*, will take care of this bit
of prestidigitation. The call looks like this:
move.l #form_dial,apb
move #FMD_START,int_in
move #0,int_in+2
move #0,int_in+4
move #10,int_in+6
move #10,int_in+8
move dial_x,int_in+10
move dial_y,int_in+12
move dial_w,int_in+14
move dial_h,int_in+16
bsr aes
After placing the address of the *form_dial* control
array into our *apb*, we place the *form_dial* operations
code into *int_in0*. The code must be one of those listed
below:
0 FMD_START......................reserves screen space
1 FMD_GROW.........................draws expanding box
2 FMD_SHRINK.......................draws shrinking box
3 FMD_FINISH...releases screen space and does a redraw
The *form_dial* function does a lot of work for us, as you
can see. This one function can handle four of our dialog
steps all by itself. For the first call to *form_dial*, we
use FMD_START code, since we want to reserve space for our
dialog.
Next, we place the X,Y coordinates, width, and height
of the smallest rectangle (we'll talk about this in a
moment); and the X,Y coordinates, width, and height of the
largest rectangle (the actual size of the dialog) into
*int_in1* through *int_in8*, respectively. Finally, we call
the AES to perform the function.
--Dialog Display Step 3--
For step 3, drawing an expanding rectangle, we again call
*form_dial*, but this time with the FMD_GROW code. The call
looks exactly the same as that for the first call, except
for the operation code. When the function is called, GEM
draws the expanding box, starting with the coordinates and
size of the smallest rectangle, and ending with the
coordinates and size of the largest rectangle. Again, the
drawing of both the expanding and shrinking boxes is
optional. If you wish, you can skip over this step and go
directly to the call below, which actually draws the dialog.
--Dialog Display Step 4--
Finally, we're ready to draw our dialog, with a call to AES
function #42, *objc_draw*:
move.l #objc_draw,apb
move #0,int_in
move #2,int_in+2
move dial_x,int_in+4
move dial_y,int_in+6
move dial_w,int_in+8
move dial_h,int_in+10
move.l dialog_addr,addr_in
bsr aes
For this call, we place into succeeding elements of
*int_in* the number of the first object to draw, how many
objects deep to draw, and the coordinates of the dialog
(which were returned by *form_center*), respectively. The
dialog coordinates in this call are called the "clipping
rectangle." The clipping rectangle is the portion of the
display to which all our screen output is limited. For
instance, if we print text that'll extend beyond the
rectangle's border, the text will be "clipped" to fit;
anything that would be drawn outside the clipping rectangle
will be ignored. Thus we can protect the integrity of the
rest of the display.
When we set *int_in0* to 0 in the above call, we're
asking that GEM start drawing the dialog with the first
object in the tree. The first object in a tree is the root--
in our case, the box containing the rest of the objects that
make up our dialog. To be sure all the objects contained
within the dialog are drawn, we must set the depth to the
proper value. If we had set it to 0, only the main box would
have been drawn. If we had set it to 1, only the main box
and its children would have been drawn, meaning that our
radio button box would be missing its buttons and our number
box would be missing its arrows. By setting depth to 2 in
the sample program, the main box plus its children and
grandchildren (the children of the children) are drawn, thus
completing our dialog. If you want to be sure you get
everything, just set depth to its maximum value of 8.
--Dialog Display Step 5--
Now that our dialog is on the screen, how do we give control
to the user? Simple! We call AES function #50, *form_do*:
move.l #form_do,apb
move #NAME,int_in
move.l dialog_addr,addr_in
bsr aes
Here, we load the index of an editable text field (0 if
there are no editable text fields) into *int_in0* and the
address of our dialog into *addr_in0*. The value in
*int_in0* tells *form_do* the number of the editable text
field we want to be active when the dialog appears.
Now that we've made our call to *form_do*, GEM will
handle the dialog for us, highlighting any selectable fields
clicked on and letting us enter text into any editable text
field. When an exit button is clicked, GEM terminates the
dialog and returns the number of the button clicked, in
*int_out0*. The button number is the only piece of
information *form_do* returns directly. If we want to see
what was entered into the strings, we have to hunt.
Obviously, only EXIT buttons will ever have their
values returned from the *form_do* call, so if you want to
know when a button is clicked, make sure, when you design
your dialog, that one of the button's attributes is EXIT or
TOUCHEXIT.
(Actually, that's not entirely true. There is another
way to get this information. Each time we click a button or
fiddle with the dialog in some other way, the object's
status is changed and recorded in the *ob_state* field of
the OBJECT structure.)
Because the only time we want to close our sample
dialog box is when the OK or CANCEL button is clicked, we
place the *form_do* call within a large loop. In the loop,
we check to see which button was clicked (by looking at
*int_out0*), and perform the necessary action. The loop
repeats, continually activating and deactivating the dialog,
until the OK or CANCEL button is clicked. Note that the call
to *form_do* doesn't redraw the dialog; it only notifies GEM
to accept more input from the form.
--Modifying a TEDINFO string--
In our sample dialog, when a button is clicked, all the
program does is print the object's name in an alert box. But
when the user clicks on one of the arrows, we have to find a
way to change the value shown in the NUMBERS object.
Let's say the user clicks on the up arrow. At that
point, program execution finds its way to the label
*chk_but8*, where *num* is incremented. The *cmpi*
instruction makes sure the displayed value doesn't exceed
9999. If *num* gets too big, we jump to the label
*num_at_max*, which calls the *change_number* subroutine,
without changing the value of *num*.
Look at the *change_number* subroutine, where we change
NUMBERS's string. The first thing we do is copy all zeroes
into *number_str*. Then, we convert the value in *num* into
ASCII form and copy it into the correct position of
*number_str*. Finally, we call *objc_draw* to redraw only
the NUMBERS object. To do this, we store the object's number
in *int_in0*, and a drawing depth of 1 into *int_in1*. For
the clipping rectangle, we use the X,Y coordinates for
NUMBERS returned to us previously. On a high resolution
screen, the width and height of this object are 96 and 32,
respectively. To run the program in another resolution,
these two numbers must be changed. Use 96 and 16 for medium
resolution. (Of course, the best way to handle these
resolution-dependent values is to use variables that you
initialize according to the current resolution, which you
check at the beginning of the program.)
--Dialog Display Step 6 & 7--
When the user has finished with the dialog, we must remove
it from the screen. We do this by performing two more
*form_dial* calls: one to display the shrinking box
(FMD_SHRINK) and one to restore the screen (FMD_FINISH).
These two calls look like this:
move.l #form_dial,apb
move #FMD_SHRINK,int_in
move #0,int_in+2
move #0,int_in+4
move #10,int_in+6
move #10,int_in+8
move dial_x,int_in+10
move dial_y,int_in+12
move dial_w,int_in+14
move dial_h,int_in+16
bsr aes
move #FMD_FINISH,int_in
bsr aes
Why is the second call (the one using FMD_FINISH) so
much shorter? Because the only parameter that changes
between the calls is the operation code, FMD_FINISH. It'd be
a waste of time to load the arrays with values that are
already there.
--Conclusion--
This finishes up our introduction to dialog boxes. By now,
you should have a good idea of how versatile they can be.
With a little creativity, you could write an entire program
that used nothing but dialog boxes for all its input and
output. Don't be afraid to experiment. Get as outrageous as
you like!
--Summary--
* An object is a data structure, the members of which
describe the object, storing all the necessary information
to bring that object up on the screen.
* The objects of a dialog box (or other type of resource)
are connected to form an object tree.
* An object can be one of several types, including Button,
String, FText, FBoxText, IBox, Box, Text, BoxChar, BoxText,
and Icon.
* An object can have several flags that control how the
object is used, including NONE, SELECTABLE, DEFAULT, EXIT,
TOUCHEXIT, RBUTTON, LASTOB, EDITABLE, HIDETREE, and
INDIRECT.
* An object can have various states, including NORMAL,
SELECTED, CHECKED, SHADOWED, OUTLINED, CROSSED, and
DISABLED.
* Editable text in a dialog box uses three fields--PTEXT,
PTMPLT, and PVALID--to tell GEM what text to display, what
part of the text is editable, and what characters the user
is allowed to input.
* Editable text fields in an object are described by a
TEDINFO structure.
* There are seven steps to displaying and handling a dialog
box. They are 1) center the dialog's coordinates, 2) reserve
screen space for the dialog, 3) draw an expanding rectangle,
4) draw the dialog, 5) activate the dialog, 6) draw a
shrinking rectangle, and 7) erase the dialog.
* The AES function #54, *form_center*, centers a dialog's
coordinates with respect to the screen.
* AES function #44, *objc_offset*, finds an object's
coordinates.
* AES function #51, *form_dial*, has four different
functions, depending on the operation code. A code of 0
reserves space on the screen for the dialog, a code of 1
displays an expanding box, a code of 2 displays a shrinking
box, and a code of 3 removes a dialog from the screen.
* AES function #42, *objc_draw*, draws an object or object
tree on the screen.
* AES function #50, *form_do*, gives control of a dialog box
to the user.
CW_PROG4.S (Source of the chapiter)
CW_PROG4MAC.S (Source of the chapiter)
CW_SAMPLE.H (Source of the chapiter)
Back to Assembly_language_tutorials