Professional GEM - Part III - The dialog handler: Difference between revisions

From Atari Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
 
Line 1: Line 1:
  +
{{Professional GEM}}
<pre>
 
   
  +
==A meaningful dialog==
   
  +
This issue of ST PRO GEM begins an exploration of ST GEM's dialog handler. I will discuss basic system calls for presenting the dialog, and then continue with techniques for initializing and reading on/off button and "radio" button objects. We will also take some short side-trips into the operation of the GEM Resource Construction Set to assist you in building these dialogs.
Professional GEM 16
 
   
  +
There are a number of short C routines which accompany this column. These are stored as file <tt>GEMCL3.XMO</tt> in DL 5 on SIG*ATARI. Before reading this column, you should visit SIG*ATARI (go pcs-132) and download this file.
   
PART III
 
   
  +
==Defining terms==
THE DIALOG HANDLER
 
   
  +
A dialog box is an "interactive form" in which the user may enter text and indicate selections by pointing with the mouse. Dialogs in GEM are "modal", that is, when a dialog is activated other screen functions such as menus and window controls are suspended until the dialog is completed.
   
  +
In most cases, the visual structure of a GEM dialog is specified within your application's resource file. The [[GEM]] Resource Construction Set (RCS) is used to build a picture of the dialog.
   
  +
When the RCS writes out a resource, it converts that picture into a tree of GEM drawing objects and stores this data structure within the resource. Before your application can display the dialog, it must load this resource file and find the address of the tree which defines the dialog.
A MEANING FULL DIALOG
 
   
  +
To load a resource, the AES checks its size and allocates memory for the load. It then reads in the resource, adjusting internal pointers to reflect the load address. Finally, the object sizes stored in the resource are converted from characters to pixels using the system font size.
This issue of ST PRO GEM begins an exploration of ST GEM's
 
dialog handler. I will discuss basic system calls for
 
presenting the dialog, and then continue with techniques for
 
initializing and reading on/off button and "radio" button
 
objects. We will also take some short side-trips into the
 
operation of the GEM Resource Construction Set to assist you in
 
building these dialogs.
 
   
  +
A note for those with Macintosh experience: Although Mac and GEM resources share a name, there are fundamental differences which can be misleading. A Mac resource is a fork within a file; a GEM resource is a TOS file by itself. Mac resources may be paged in and out of memory; GEM resources are monolithic. GEM resources are internally tree structured; Mac resources are not. Finally, Mac resources include font information, while ST GEM does this with font loading at the VDI level.
There are a number of short C routines which accompany this
 
column. These are stored as file GEMCL3.XMO in DL 5 on
 
SIG*ATARI. Before reading this column, you should visit
 
SIG*ATARI (go pcs-132) and download this file.
 
   
  +
The resource load is done with the GEM AES call:
   
  +
<pre>
  +
ok = rsrcload(ADDR("MYAPP.RSC"));
  +
</pre>
   
  +
"MYAPP" should be replaced with the name of your program. Resources conventionally have the same primary name as their application, with the RSC extent name instead of PRG. The ok flag returned by rsrcload will be FALSE is anything went wrong during the load.
DEFENING TERMS
 
   
  +
The most common causes of failure are the resource not being in the application's subdirectory, or lack of sufficient memory for GEM to allocate space for the resource. If this happens, you must terminate the program immediately.
A dialog box is an "interactive form" in which the user may
 
enter text and indicate selections by pointing with the mouse.
 
Dialogs in GEM are "modal", that is, when a dialog is activated
 
other screen functions such as menus and window controls are
 
suspended until the dialog is completed.
 
   
  +
Once you have loaded the resource, you find the address of a dialog's object tree with:
In most cases, the visual structure of a GEM dialog is
 
specified within your application's resource file. The GEM
 
Resource Construction Set (RCS) is used to build a picture of
 
the dialog.
 
   
  +
<pre>
When the RCS writes out a resource, it converts that
 
  +
rsrcgaddr(RTREE, MYDIALOG, &tree);
picture into a tree of GEM drawing objects and stores this data
 
  +
</pre>
structure within the resource. Before your application can
 
display the dialog, it must load this resource file and find
 
the address of the tree which defines the dialog.
 
   
  +
Tree is a 32-bit variable which will receive the address of the root node of the tree.
To load a resource, the AES checks its size and allocates
 
memory for the load. It then reads in the resource, adjusting
 
internal pointers to reflect the load address. Finally, the
 
object sizes stored in the resource are converted from
 
characters to pixels using the system font size.
 
   
  +
The mnemonic MYDIALOG should be replaced with the name you gave your dialog when defining it in the RCS. At the same time that it writes the resource, RCS generates a corresponding <tt>.H</tt> file containing tree and object names. In order to use these mnemonics within your program, you must include the name file in your compile: <tt>#include "MYAPP.H"</tt>
A note for those with Macintosh experience: Although Mac
 
and GEM resources share a name, there are fundamental
 
differences which can be misleading. A Mac resource is a fork
 
within a file; a GEM resource is a TOS file by itself. Mac
 
   
   
  +
==Bug alert!==
 
   
  +
When using the DRI/Alcyon C compiler, .H files must be in the compiler's home directory or they will not be found. This is especially annoying using a two floppy drive ST development system. The only way around this is to explicitly reference an alternate disk in the <tt>#include</tt>, for instance: <tt>"B:MYAPP.H"</tt>. [Ed. Note: Use the <tt>-i</tt> flag with the C pre-processor to name the include directories].
   
  +
Now that the address of the dialog tree has been found, you are ready to display it. The standard (and minimal) sequence for doing so is given in routine hndldial() in the download. We will now walk through each step in this procedure.
Professional GEM Part III 17
 
   
  +
The formcenter call establishes the location of the dialog on the screen. Dialog trees generated by the [[RCS]] have an undefined origin (upper-left corner).
   
  +
Formcenter computes the upper-left location necessary to center the dialog on the screen, and inserts it into the <tt>OBX</tt> and <tt>OBY</tt> fields of the ROOT object of the tree. It also computes the screen rectangle which the dialog will occupy on screen and writes its pixel coordinates into variables <tt>xdial</tt>, <tt>ydial</tt>, <tt>wdial</tt>, and <tt>hdial</tt>.
resources may be paged in and out of memory; GEM resources are
 
monolithic. GEM resources are internally tree structured; Mac
 
resources are not. Finally, Mac resources include font
 
information, while ST GEM does this with font loading at the VDI
 
level.
 
   
  +
There is one peculiarity of formcenter which occasionally causes trouble. Normally the rectangle returned in <tt>xdial</tt>, etc., is exactly the same size as the basic dialog box.
The resource load is done with the GEM AES call:
 
   
  +
However, when the <tt>OUTLINED</tt> enhancement has been specified for the box, formcenter adds a three pixel margin to the rectangle returned. This causes the screen area under the outline to be correctly redrawn later (see below). Note that <tt>OUTLINED</tt> is part of the standard dialog box in the RCS. Other enhancements, such as SHADOWED or "outside" borders are NOT handled in this fashion, and you must compensate for them in your code.
ok = rsrcload(ADDR("MYAPP.RSC"));
 
   
  +
The next part of the sequence is a formdial call with a zero parameter. This reserves the screen for the dialog action about to occur. Note that the [[C]] binding given for <tt>formdial</tt> in the DRI documents is in error: there are nine parameters, not five. The first set of xywh arguments is actually used with <tt>formdial</tt> calls 1 and 2 only, but place holders must be supplied in all cases.
"MYAPP" should be replaced with the name of your program.
 
Resources conventionally have the same primary name as their
 
application, with the RSC extent name instead of PRG. The ok
 
flag returned by rsrcload will be FALSE is anything went wrong
 
during the load.
 
   
  +
The succeeding formdial call (parameter one) animates a "zoom box" on the screen which moves and grows from the first screen rectangle given to the second rectangle, where the dialog will be displayed.
The most common causes of failure are the resource not
 
being in the application's subdirectory, or lack of sufficient
 
memory for GEM to allocate space for the resource. If this
 
happens, you must terminate the program immediately.
 
   
  +
The use of this call is entirely optional. In choosing whether to use it or not, you should consider whether the origin of the "zoom" is relevant to the operation. For instance, a zoom from the menu bar is relatively meaningless, while a zoom from an object about to be edited in the dialog provides visual feedback to the user, showing whether the correct object was chosen.
Once you have loaded the resource, you find the address of
 
a dialog's object tree with:
 
   
  +
If the origin is not relevant, then the zoom is just a time-waster. If you decide to include these effects, consider a "preferences" option in your app which will allow the experienced and jaded user to turn them off in the interests of speed.
rsrcgaddr(RTREE, MYDIALOG, &tree);
 
   
  +
The objcdraw call actually displays the dialog on the screen. Note that the address of the tree, the beginning drawing object, and the drawing depth are passed as arguments, as well as the rectangle allotted for the dialog.
Tree is a 32-bit variable which will receive the address of the
 
root node of the tree.
 
   
  +
In general, dialogs (and parts of dialogs) are ALWAYS drawn beginning at the ROOT (object zero). When you want to draw only a portion of the dialog, adjust the clipping rectangle, but not the object number. This ensures that the background of the dialog is always drawn correctly.
The mnemonic MYDIALOG should be replaced with the name you
 
gave your dialog when defining it in the RCS. At the same time
 
that it writes the resource, RCS generates a corresponding .H
 
file containing tree and object names. In order to use these
 
mnemonics within your program, you must include the name file in
 
your compile: #include "MYAPP.H"
 
   
  +
The <tt>objcxywh()</tt> utility in the download can be used to find the clipping rectangle for any object within a dialog, though you may have to allow an extra margin is you have used shadows, outlines, or outside borders with the object.
   
  +
Calling <tt>formdo</tt> transfers control to the AES, which animates the dialog for user interaction. The address of the dialog tree is passed as a parameter. The second paramter is the number of the editable object at which the text cursor will first be positioned. If you have no text fields, pass a zero. Note that again the DRI documents are in error: passing a -1 default may crash the system. Also be careful that the default which you specify is actually a text field; no error checking is performed.
BUG ALERT !
 
   
  +
The formdo call returns the number of the object on which the clicked to terminate the dialog. Usually this is a button type object with the <tt>EXIT<tt> and <tt>SELECTABLE<tt> attributes set. Setting the <tt>DEFAULT</tt> attribute as well will cause an exit on that object is a carriage return is struck while in the dialog.
When using the DRI/Alcyon C compiler, .H files must be in
 
the compiler's home directory or they will not be found. This
 
is especially annoying using a two floppy drive ST development
 
system. The only way around this is to explicitly reference an
 
alternate disk in the #include, for instance: "B:MYAPP.H".
 
[Ed. Note: Use the -i flag with the C pre-processor to name the
 
include directories].
 
   
  +
If the top bit of the return is set, it indicates that the exit object had the <tt>TOUCHEXIT</tt> attribute and was selected with a double-click. Since very few dialogs use this combination, the sample code simply masks off the top bit.
Now that the address of the dialog tree has been found, you
 
are ready to display it. The standard (and minimal) sequence
 
for doing so is given in routine hndldial() in the download. We
 
will now walk through each step in this procedure.
 
   
  +
The next formdial call reverses the "zoom box", moving it from the dialog's location back to the given x,y,w,h. The same cautions apply here as above.
   
  +
The final formdial call tells GEM that the dialog is complete, and that the screen area occupied by the dialog is now considered "dirty" and needs to be redrawn. Using the methods described in our last column, GEM then sends redraws to all windows which were overlaid, and does any necessary redrawing of the menu or desktop itself.
   
  +
There is one notable "feature" of formdial(3): It always redraws an area which is two pixels wider and higher than yourrequest! This was probably included to make sure that drop-shadows were cleaned up, and is usually innocuous.
 
   
   
  +
==A handy trick==
Professional GEM Part III 18
 
   
  +
Use of the formdial(3) call is not limited to dialogs. You can use it to force the system to redraw any part of the screen. The advantage of this method is that the redraw area need not lie entirely within a window, as was necessary with the sendredraw method detailed in the last column. A disadvantage is that this method is somewhat slower, since the AES has to decide who gets the redraws.
   
   
  +
==Clean up==
The formcenter call establishes the location of the dialog
 
on the screen. Dialog trees generated by the RCS have an
 
undefined origin (upper-left corner).
 
   
  +
As a last step, you need to clear the <tt>SELECTED</tt> flag in the object which was clicked. If you do not do this, the object will be drawn inverted the next time you call the dialog. You could clear the flag with the GEM <tt>objcchange</tt> call, but it is inefficient since you do not need to redraw the object.
Formcenter computes the upper-left location necessary to
 
center the dialog on the screen, and inserts it into the OBX and
 
OBY fields of the ROOT object of the tree. It also computes the
 
screen rectangle which the dialog will occupy on screen and
 
writes its pixel coordinates into variables xdial, ydial, wdial,
 
and hdial.
 
   
  +
Instead, use the deselobj() code in the download, which modifies the object's <tt>OBSTATE</tt> field directly. Assuming that retobj contains the exit object returned by <tt>hndldial</tt>, the call:
There is one peculiarity of formcenter which occasionally
 
causes trouble. Normally the rectangle returned in xdial, etc.,
 
is exactly the same size as the basic dialog box.
 
   
  +
<pre>
However, when the OUTLINED enhancement has been specified
 
  +
deselobj(tree, retobj);
for the box, formcenter adds a three pixel margin to the
 
  +
</pre>
rectangle returned. This causes the screen area under the
 
outline to be correctly redrawn later (see below). Note that
 
OUTLINED is part of the standard dialog box in the RCS. Other
 
enhancements, such as SHADOWED or "outside" borders are NOT
 
handled in this fashion, and you must compensate for them in
 
your code.
 
   
  +
will do the trick.
The next part of the sequence is a formdial call with a
 
zero parameter. This reserves the screen for the dialog action
 
about to occur. Note that the C binding given for formdial in
 
the DRI documents is in error: there are nine parameters, not
 
five. The first set of xywh arguments is actually used with
 
formdial calls 1 and 2 only, but place holders must be supplied
 
in all cases.
 
   
The succeeding formdial call (parameter one) animates a
 
"zoom box" on the screen which moves and grows from the first
 
screen rectangle given to the second rectangle, where the dialog
 
will be displayed.
 
   
  +
==Recap==
The use of this call is entirely optional. In choosing
 
whether to use it or not, you should consider whether the origin
 
of the "zoom" is relevant to the operation. For instance, a
 
zoom from the menu bar is relatively meaningless, while a zoom
 
from an object about to be edited in the dialog provides visual
 
feedback to the user, showing whether the correct object was
 
chosen.
 
   
  +
The basic dialog handling method I have described contains three steps: initialization (rsrcgaddr), dialog presentation (<tt>hndldial</tt>), and cleanup (<tt>deselobj</tt>).
If the origin is not relevant, then the zoom is just a
 
time-waster. If you decide to include these effects, consider a
 
"preferences" option in your app which will allow the
 
experienced and jaded user to turn them off in the interests of
 
speed.
 
   
  +
As we build more advanced dialogs, these same basic steps will be performed, but they will grow more complex. The initialization will include setting up proper object text and states, and the cleanup phase will also interrogate the final states of objects to find out what the user did.
   
   
  +
==Button Button==
 
   
  +
The simple dialogs described above contain only exit buttons as active objects. As such, they are little more than glorified alert boxes.
   
  +
We will now increase the complexity a little by considering non-exit buttons. These are constructed by setting the <tt>SELECTABLE</tt> attribute on a button object. At run-time, such an object will toggle its state between selected (highlighted) and non-selected whenever the user clicks on it. (You can set the <tt>SELECTABLE</tt> attribute of other types of objects and use them instead of actual buttons, but be sure that the user will be able to figure out what you intend!)
Professional GEM Part III 19
 
   
  +
Having non-exit buttons forces us to consider the problem of initializing them before the dialog, and interrogating and resetting them afterward.
   
  +
Since a button is a toggle, it is usually associated with a flag variable in the program. As part of the initialization, you should test the flag variable, and if true call:
   
  +
<pre>
The objcdraw call actually displays the dialog on the
 
  +
selobj(tree, BTNOBJ);
screen. Note that the address of the tree, the beginning drawing
 
  +
</pre>
object, and the drawing depth are passed as arguments, as well
 
as the rectangle allotted for the dialog.
 
   
  +
which will cause the button to appear highlighted when the dialog is first drawn. <tt>Selobj()</tt> is in the download. <tt>BTNOBJ</tt> is replaced with the name you gave your button when you defined it in the [[RCS]]. Since the button starts out deselected, you don't have to do anything if your flag variable is false.
In general, dialogs (and parts of dialogs) are ALWAYS
 
drawn beginning at the ROOT (object zero). When you want to
 
draw only a portion of the dialog, adjust the clipping
 
rectangle, but not the object number. This ensures that the
 
background of the dialog is always drawn correctly.
 
   
  +
After the dialog has completed, you need to check the object's state. The <tt>selectp()</tt> utility does so by masking the <tt>OBSTATE</tt> field. You can simply assign the result of this test to your flag variable, but be sure that the dialog was exited with an OK button, not with a CANCEL! Again, remember to clean up the button with <tt>deselobj()</tt>. (It's often easiest to deselect all buttons just before you leave the dialog routine, regardless of the final dialog state.)
The objcxywh() utility in the download can be used to find
 
the clipping rectangle for any object within a dialog, though
 
you may have to allow an extra margin is you have used shadows,
 
outlines, or outside borders with the object.
 
   
Calling formdo transfers control to the AES, which animates
 
the dialog for user interaction. The address of the dialog tree
 
is passed as a parameter. The second paramter is the number of
 
the editable object at which the text cursor will first be
 
positioned. If you have no text fields, pass a zero. Note that
 
again the DRI documents are in error: passing a -1 default may
 
crash the system. Also be careful that the default which you
 
specify is actually a text field; no error checking is
 
performed.
 
   
  +
==Who's got the button?==
The formdo call returns the number of the object on which
 
the clicked to terminate the dialog. Usually this is a button
 
type object with the EXIT and SELECTABLE attributes set.
 
Setting the DEFAULT attribute as well will cause an exit on that
 
object is a carriage return is struck while in the dialog.
 
   
  +
Another common use of buttons in a dialog is to select one of a set of possible options. In GEM, such objects are called radio buttons. This term recalls automobile radio tuners where pushing in one button pops out any others. In like fashion, selecting any one of a set of radio buttons automatically deselects all of the others.
If the top bit of the return is set, it indicates that the
 
exit object had the TOUCHEXIT attribute and was selected with a
 
double-click. Since very few dialogs use this combination, the
 
sample code simply masks off the top bit.
 
   
  +
To use the radio button feature, you must do some careful work with the Resource Construction Set.
The next formdial call reverses the "zoom box", moving it
 
from the dialog's location back to the given x,y,w,h. The same
 
cautions apply here as above.
 
   
  +
First, each member of a set of radio buttons must be children of the same parent object within the object tree. To create this structure, put a hollow box type object in the dialog, make it big enough to hold all of the buttons, and then put the buttons into the box one at a time.
The final formdial call tells GEM that the dialog is
 
complete, and that the screen area occupied by the dialog is now
 
considered "dirty" and needs to be redrawn. Using the methods
 
described in our last column, GEM then sends redraws to all
 
windows which were overlaid, and does any necessary redrawing of
 
the menu or desktop itself.
 
   
  +
By nesting the buttons within the box object, you force them to be its children. Each of the buttons must have both the <tt>SELECTABLE</tt> and <tt>RADIO BUTTON</tt> attributes set. When you are done, you may make the containing box invisible by setting its border to zero, but do not FLATTEN it!
There is one notable "feature" of formdial(3): It always
 
redraws an area which is two pixels wider and higher than your
 
request! This was probably included to make sure that
 
   
  +
Since each radio button represents a different option, you must usually assign a name to each object. When initializing the dialog, you must check which option is currently set, and turn on the corresponding button only. A chain of if-then-else structures assures that only one button will be selected.
   
  +
At the conclusion of the dialog, you must check each button with selectp() and make the appropriate adjustments to internal variables. Again, an if-then-else chain is appropriate since only one button may be selected. Either deselect the chosen button within this chain or do them all at the end.
 
   
  +
There is one common use of radio buttons in which you may short-cut this procedure. If the buttons each represent one possible value of a numeric variable, for instance, a set of selector buttons representing colors from zero to seven, then you can compute the initial object directly.
   
  +
In order for this technique to work, you must use a special capability of the RCS. Insert the object corresponding to a zero value at the top (or left) of your array of buttons, then put the "one" button below (or right) of it, and so on.
Professional GEM Part III 20
 
   
  +
When the buttons are complete, the SORT operation is used to guarantee that the top/left object is in fact the first child of the parent box with the others following in order. Due to the details of object tree structure (to be discussed in the next column), this will guarantee that these objects are contiguous in the resource.
   
  +
If you assign a name (say BUTTON1) to the first button, then you can initialize the correct button with the call:
drop-shadows were cleaned up, and is usually innocuous.
 
   
  +
<pre>
  +
selobj(tree, BUTTON1 + field);
  +
</pre>
   
  +
where field is the variable of interest.
A HANDY TRICK
 
   
  +
When the dialog is complete, you can scan the radio buttons to compute the new value for the underlying variable. The <tt>encode()</tt> procedure in the download will do this. As always, remember to deselect the buttons at the end.
Use of the formdial(3) call is not limited to dialogs. You
 
can use it to force the system to redraw any part of the
 
screen. The advantage of this method is that the redraw area
 
need not lie entirely within a window, as was necessary with the
 
sendredraw method detailed in the last column. A disadvantage
 
is that this method is somewhat slower, since the AES has to
 
decide who gets the redraws.
 
   
  +
You can use offsets or multipliers if your variable's values don't start with zero or increment by one. If the values are irregular you may be able to use a lookup table, at the cost of additional code.
   
CLEAN UP
 
   
  +
==Coming up next==
As a last step, you need to clear the SELECTED flag in the
 
object which was clicked. If you do not do this, the object
 
will be drawn inverted the next time you call the dialog. You
 
could clear the flag with the GEM objcchange call, but it is
 
inefficient since you do not need to redraw the object.
 
   
  +
In the next column, I will discuss the internal structure of object trees. Then we'll use that knowledge to build a piece of code which will "walk" an entire tree and apply a function to each object. We'll apply this code to do all of the button deselects with a single call! I'll also look at handling editable text fields and discuss some ways to alter a dialog's appearance at run-time.
Instead, use the deselobj() code in the download, which
 
modifies the object's OBSTATE field directly. Assuming that
 
retobj contains the exit object returned by hndldial, the call:
 
   
deselobj(tree, retobj);
 
   
  +
==Dispel gremlins==
will do the trick.
 
 
 
RECAP
 
 
The basic dialog handling method I have described contains
 
three steps: initialization (rsrcgaddr), dialog presentation
 
(hndldial), and cleanup (deselobj).
 
 
As we build more advanced dialogs, these same basic steps
 
will be performed, but they will grow more complex. The
 
initialization will include setting up proper object text and
 
states, and the cleanup phase will also interrogate the final
 
states of objects to find out what the user did.
 
 
 
BUTTON BUTTON
 
 
The simple dialogs described above contain only exit
 
buttons as active objects. As such, they are little more than
 
glorified alert boxes.
 
 
We will now increase the complexity a little by considering
 
non-exit buttons. These are constructed by setting the
 
 
 
 
 
 
Professional GEM Part III 21
 
 
 
SELECTABLE attribute on a button object. At run-time, such an
 
object will toggle its state between selected (highlighted) and
 
non-selected whenever the user clicks on it. (You can set the
 
SELECTABLE attribute of other types of objects and use them
 
instead of actual buttons, but be sure that the user will be
 
able to figure out what you intend!)
 
 
Having non-exit buttons forces us to consider the problem
 
of initializing them before the dialog, and interrogating and
 
resetting them afterward.
 
 
Since a button is a toggle, it is usually associated with a
 
flag variable in the program. As part of the initialization,
 
you should test the flag variable, and if true call:
 
 
selobj(tree, BTNOBJ);
 
 
which will cause the button to appear highlighted when the
 
dialog is first drawn. Selobj() is in the download. BTNOBJ is
 
replaced with the name you gave your button when you defined it
 
in the RCS. Since the button starts out deselected, you don't
 
have to do anything if your flag variable is false.
 
 
After the dialog has completed, you need to check the
 
object's state. The selectp() utility does so by masking the
 
OBSTATE field. You can simply assign the result of this test to
 
your flag variable, but be sure that the dialog was exited with
 
an OK button, not with a CANCEL! Again, remember to clean up the
 
button with deselobj(). (It's often easiest to deselect all
 
buttons just before you leave the dialog routine, regardless of
 
the final dialog state.)
 
 
 
WHO'S GOT THE BUTTON ?
 
 
Another common use of buttons in a dialog is to select one
 
of a set of possible options. In GEM, such objects are called
 
radio buttons. This term recalls automobile radio tuners where
 
pushing in one button pops out any others. In like fashion,
 
selecting any one of a set of radio buttons automatically
 
deselects all of the others.
 
 
To use the radio button feature, you must do some careful
 
work with the Resource Construction Set.
 
 
First, each member of a set of radio buttons must be
 
children of the same parent object within the object tree. To
 
create this structure, put a hollow box type object in the
 
dialog, make it big enough to hold all of the buttons, and then
 
put the buttons into the box one at a time.
 
 
 
 
 
 
 
 
Professional GEM Part III 22
 
 
 
 
By nesting the buttons within the box object, you force
 
them to be its children. Each of the buttons must have both the
 
SELECTABLE and RADIO BUTTON attributes set. When you are done,
 
you may make the containing box invisible by setting its border
 
to zero, but do not FLATTEN it!
 
 
Since each radio button represents a different option, you
 
must usually assign a name to each object. When initializing
 
the dialog, you must check which option is currently set, and
 
turn on the corresponding button only. A chain of if-then-else
 
structures assures that only one button will be selected.
 
 
At the conclusion of the dialog, you must check each button
 
with selectp() and make the appropriate adjustments to internal
 
variables. Again, an if-then-else chain is appropriate since
 
only one button may be selected. Either deselect the chosen
 
button within this chain or do them all at the end.
 
 
There is one common use of radio buttons in which you may
 
short-cut this procedure. If the buttons each represent one
 
possible value of a numeric variable, for instance, a set of
 
selector buttons representing colors from zero to seven, then
 
you can compute the initial object directly.
 
 
In order for this technique to work, you must use a special
 
capability of the RCS. Insert the object corresponding to a
 
zero value at the top (or left) of your array of buttons, then
 
put the "one" button below (or right) of it, and so on.
 
 
When the buttons are complete, the SORT operation is used
 
to guarantee that the top/left object is in fact the first child
 
of the parent box with the others following in order. Due to
 
the details of object tree structure (to be discussed in the
 
next column), this will guarantee that these objects are
 
contiguous in the resource.
 
 
If you assign a name (say BUTTON1) to the first button,
 
then you can initialize the correct button with the call:
 
 
selobj(tree, BUTTON1 + field);
 
 
where field is the variable of interest.
 
 
When the dialog is complete, you can scan the radio buttons
 
to compute the new value for the underlying variable. The
 
encode() procedure in the download will do this. As always,
 
remember to deselect the buttons at the end.
 
 
You can use offsets or multipliers if your variable's
 
values don't start with zero or increment by one. If the values
 
are irregular you may be able to use a lookup table, at the cost
 
 
 
 
 
 
Professional GEM Part III 23
 
 
 
of additional code.
 
 
 
COMING UP NEXT
 
 
In the next column, I will discuss the internal structure
 
of object trees. Then we'll use that knowledge to build a piece
 
of code which will "walk" an entire tree and apply a function to
 
each object. We'll apply this code to do all of the button
 
deselects with a single call! I'll also look at handling
 
editable text fields and discuss some ways to alter a dialog's
 
appearance at run-time.
 
 
 
DISPEL GREMLINS
 
 
An editing error caused an omission in the first
 
installment of ST PRO GEM. The window components RTARROW and
 
DNARROW should have been listed along with HSLIDE as the
 
horizontal equivalents of the vertical slider components which
 
were discussed.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
</pre>
 
   
  +
An editing error caused an omission in the first installment of ST PRO GEM. The window components <tt>RTARROW</tt> and <tt>DNARROW</tt> should have been listed along with <tt>HSLIDE</tt> as the horizontal equivalents of the vertical slider components which were discussed.
Back to [[Professional_GEM]]
 

Latest revision as of 21:05, 12 October 2006

Professional GEM
Part I -- Windows
In the beginningOpen sesameCleaning upThose fat slidersComing up nextFeedback
Part II -- Windows
ExcelsiorRedrawing windowsCaveat emptorInto the bitsA small confessionWindow control requestWindow slider messagesA common bugDept. of dirty tricksA sin of omissionComing soon
Part III -- The dialog handler
A meaningful dialogDefining termsBug alert!A handy trickClean upRecapButton ButtonWho's got the button?Coming up nextDispell gremlins
Part IV -- Resource structure
A maze of twisty little passagesPutting it to workLetters, we get lettersStraw poll!Stay tuned!
Part V -- Resource tree structures
How GEM does itThought experimentsA treewalker of our own
Part VI -- Raster operations
Seasons greetingsDefining termsMonochrome vs. colorStandard vs. device-specific formatEven-word vs. fringesMFDB'sLet's operateTransform formCopy raster opaqueCopy raster transparentThe mode parameterReplace modeErase modeXor modeTransparent modeReverse transparent modeThe problem of colorOptimizing raster operationsAvoid merged copiesMove to corresponding pixelsAvoid fringesUse another methodFeedback resultsThe next questionComing up soon
Part VII -- Menu structures
Happy new yearMenu basicsMenu structuresUsing the menuGetting fancyCheck please?Now you see it now you don'tLunch and dinner menusDo it yourselfMake prettyThat's it for now!
Part VIII -- User interfaces
And now for something completely different!Credit where it's dueFingertipsMusclesEyesShort-term memoryChunkingThink!Are we not men?Of modes and bandwidthTo do is to be!Amen...
Part IX -- VDI Graphics: Lines and solids
A bit of historyThe line forms on the leftSolidsTo be continued
Appendices
Main page

A meaningful dialog

This issue of ST PRO GEM begins an exploration of ST GEM's dialog handler. I will discuss basic system calls for presenting the dialog, and then continue with techniques for initializing and reading on/off button and "radio" button objects. We will also take some short side-trips into the operation of the GEM Resource Construction Set to assist you in building these dialogs.

There are a number of short C routines which accompany this column. These are stored as file GEMCL3.XMO in DL 5 on SIG*ATARI. Before reading this column, you should visit SIG*ATARI (go pcs-132) and download this file.


Defining terms

A dialog box is an "interactive form" in which the user may enter text and indicate selections by pointing with the mouse. Dialogs in GEM are "modal", that is, when a dialog is activated other screen functions such as menus and window controls are suspended until the dialog is completed.

In most cases, the visual structure of a GEM dialog is specified within your application's resource file. The GEM Resource Construction Set (RCS) is used to build a picture of the dialog.

When the RCS writes out a resource, it converts that picture into a tree of GEM drawing objects and stores this data structure within the resource. Before your application can display the dialog, it must load this resource file and find the address of the tree which defines the dialog.

To load a resource, the AES checks its size and allocates memory for the load. It then reads in the resource, adjusting internal pointers to reflect the load address. Finally, the object sizes stored in the resource are converted from characters to pixels using the system font size.

A note for those with Macintosh experience: Although Mac and GEM resources share a name, there are fundamental differences which can be misleading. A Mac resource is a fork within a file; a GEM resource is a TOS file by itself. Mac resources may be paged in and out of memory; GEM resources are monolithic. GEM resources are internally tree structured; Mac resources are not. Finally, Mac resources include font information, while ST GEM does this with font loading at the VDI level.

The resource load is done with the GEM AES call:

 ok = rsrcload(ADDR("MYAPP.RSC"));

"MYAPP" should be replaced with the name of your program. Resources conventionally have the same primary name as their application, with the RSC extent name instead of PRG. The ok flag returned by rsrcload will be FALSE is anything went wrong during the load.

The most common causes of failure are the resource not being in the application's subdirectory, or lack of sufficient memory for GEM to allocate space for the resource. If this happens, you must terminate the program immediately.

Once you have loaded the resource, you find the address of a dialog's object tree with:

 rsrcgaddr(RTREE, MYDIALOG, &tree);

Tree is a 32-bit variable which will receive the address of the root node of the tree.

The mnemonic MYDIALOG should be replaced with the name you gave your dialog when defining it in the RCS. At the same time that it writes the resource, RCS generates a corresponding .H file containing tree and object names. In order to use these mnemonics within your program, you must include the name file in your compile: #include "MYAPP.H"


Bug alert!

When using the DRI/Alcyon C compiler, .H files must be in the compiler's home directory or they will not be found. This is especially annoying using a two floppy drive ST development system. The only way around this is to explicitly reference an alternate disk in the #include, for instance: "B:MYAPP.H". [Ed. Note: Use the -i flag with the C pre-processor to name the include directories].

Now that the address of the dialog tree has been found, you are ready to display it. The standard (and minimal) sequence for doing so is given in routine hndldial() in the download. We will now walk through each step in this procedure.

The formcenter call establishes the location of the dialog on the screen. Dialog trees generated by the RCS have an undefined origin (upper-left corner).

Formcenter computes the upper-left location necessary to center the dialog on the screen, and inserts it into the OBX and OBY fields of the ROOT object of the tree. It also computes the screen rectangle which the dialog will occupy on screen and writes its pixel coordinates into variables xdial, ydial, wdial, and hdial.

There is one peculiarity of formcenter which occasionally causes trouble. Normally the rectangle returned in xdial, etc., is exactly the same size as the basic dialog box.

However, when the OUTLINED enhancement has been specified for the box, formcenter adds a three pixel margin to the rectangle returned. This causes the screen area under the outline to be correctly redrawn later (see below). Note that OUTLINED is part of the standard dialog box in the RCS. Other enhancements, such as SHADOWED or "outside" borders are NOT handled in this fashion, and you must compensate for them in your code.

The next part of the sequence is a formdial call with a zero parameter. This reserves the screen for the dialog action about to occur. Note that the C binding given for formdial in the DRI documents is in error: there are nine parameters, not five. The first set of xywh arguments is actually used with formdial calls 1 and 2 only, but place holders must be supplied in all cases.

The succeeding formdial call (parameter one) animates a "zoom box" on the screen which moves and grows from the first screen rectangle given to the second rectangle, where the dialog will be displayed.

The use of this call is entirely optional. In choosing whether to use it or not, you should consider whether the origin of the "zoom" is relevant to the operation. For instance, a zoom from the menu bar is relatively meaningless, while a zoom from an object about to be edited in the dialog provides visual feedback to the user, showing whether the correct object was chosen.

If the origin is not relevant, then the zoom is just a time-waster. If you decide to include these effects, consider a "preferences" option in your app which will allow the experienced and jaded user to turn them off in the interests of speed.

The objcdraw call actually displays the dialog on the screen. Note that the address of the tree, the beginning drawing object, and the drawing depth are passed as arguments, as well as the rectangle allotted for the dialog.

In general, dialogs (and parts of dialogs) are ALWAYS drawn beginning at the ROOT (object zero). When you want to draw only a portion of the dialog, adjust the clipping rectangle, but not the object number. This ensures that the background of the dialog is always drawn correctly.

The objcxywh() utility in the download can be used to find the clipping rectangle for any object within a dialog, though you may have to allow an extra margin is you have used shadows, outlines, or outside borders with the object.

Calling formdo transfers control to the AES, which animates the dialog for user interaction. The address of the dialog tree is passed as a parameter. The second paramter is the number of the editable object at which the text cursor will first be positioned. If you have no text fields, pass a zero. Note that again the DRI documents are in error: passing a -1 default may crash the system. Also be careful that the default which you specify is actually a text field; no error checking is performed.

The formdo call returns the number of the object on which the clicked to terminate the dialog. Usually this is a button type object with the EXIT and SELECTABLE attributes set. Setting the DEFAULT attribute as well will cause an exit on that object is a carriage return is struck while in the dialog.

If the top bit of the return is set, it indicates that the exit object had the TOUCHEXIT attribute and was selected with a double-click. Since very few dialogs use this combination, the sample code simply masks off the top bit.

The next formdial call reverses the "zoom box", moving it from the dialog's location back to the given x,y,w,h. The same cautions apply here as above.

The final formdial call tells GEM that the dialog is complete, and that the screen area occupied by the dialog is now considered "dirty" and needs to be redrawn. Using the methods described in our last column, GEM then sends redraws to all windows which were overlaid, and does any necessary redrawing of the menu or desktop itself.

There is one notable "feature" of formdial(3): It always redraws an area which is two pixels wider and higher than yourrequest! This was probably included to make sure that drop-shadows were cleaned up, and is usually innocuous.


A handy trick

Use of the formdial(3) call is not limited to dialogs. You can use it to force the system to redraw any part of the screen. The advantage of this method is that the redraw area need not lie entirely within a window, as was necessary with the sendredraw method detailed in the last column. A disadvantage is that this method is somewhat slower, since the AES has to decide who gets the redraws.


Clean up

As a last step, you need to clear the SELECTED flag in the object which was clicked. If you do not do this, the object will be drawn inverted the next time you call the dialog. You could clear the flag with the GEM objcchange call, but it is inefficient since you do not need to redraw the object.

Instead, use the deselobj() code in the download, which modifies the object's OBSTATE field directly. Assuming that retobj contains the exit object returned by hndldial, the call:

 deselobj(tree, retobj);

will do the trick.


Recap

The basic dialog handling method I have described contains three steps: initialization (rsrcgaddr), dialog presentation (hndldial), and cleanup (deselobj).

As we build more advanced dialogs, these same basic steps will be performed, but they will grow more complex. The initialization will include setting up proper object text and states, and the cleanup phase will also interrogate the final states of objects to find out what the user did.


Button Button

The simple dialogs described above contain only exit buttons as active objects. As such, they are little more than glorified alert boxes.

We will now increase the complexity a little by considering non-exit buttons. These are constructed by setting the SELECTABLE attribute on a button object. At run-time, such an object will toggle its state between selected (highlighted) and non-selected whenever the user clicks on it. (You can set the SELECTABLE attribute of other types of objects and use them instead of actual buttons, but be sure that the user will be able to figure out what you intend!)

Having non-exit buttons forces us to consider the problem of initializing them before the dialog, and interrogating and resetting them afterward.

Since a button is a toggle, it is usually associated with a flag variable in the program. As part of the initialization, you should test the flag variable, and if true call:

 selobj(tree, BTNOBJ);

which will cause the button to appear highlighted when the dialog is first drawn. Selobj() is in the download. BTNOBJ is replaced with the name you gave your button when you defined it in the RCS. Since the button starts out deselected, you don't have to do anything if your flag variable is false.

After the dialog has completed, you need to check the object's state. The selectp() utility does so by masking the OBSTATE field. You can simply assign the result of this test to your flag variable, but be sure that the dialog was exited with an OK button, not with a CANCEL! Again, remember to clean up the button with deselobj(). (It's often easiest to deselect all buttons just before you leave the dialog routine, regardless of the final dialog state.)


Who's got the button?

Another common use of buttons in a dialog is to select one of a set of possible options. In GEM, such objects are called radio buttons. This term recalls automobile radio tuners where pushing in one button pops out any others. In like fashion, selecting any one of a set of radio buttons automatically deselects all of the others.

To use the radio button feature, you must do some careful work with the Resource Construction Set.

First, each member of a set of radio buttons must be children of the same parent object within the object tree. To create this structure, put a hollow box type object in the dialog, make it big enough to hold all of the buttons, and then put the buttons into the box one at a time.

By nesting the buttons within the box object, you force them to be its children. Each of the buttons must have both the SELECTABLE and RADIO BUTTON attributes set. When you are done, you may make the containing box invisible by setting its border to zero, but do not FLATTEN it!

Since each radio button represents a different option, you must usually assign a name to each object. When initializing the dialog, you must check which option is currently set, and turn on the corresponding button only. A chain of if-then-else structures assures that only one button will be selected.

At the conclusion of the dialog, you must check each button with selectp() and make the appropriate adjustments to internal variables. Again, an if-then-else chain is appropriate since only one button may be selected. Either deselect the chosen button within this chain or do them all at the end.

There is one common use of radio buttons in which you may short-cut this procedure. If the buttons each represent one possible value of a numeric variable, for instance, a set of selector buttons representing colors from zero to seven, then you can compute the initial object directly.

In order for this technique to work, you must use a special capability of the RCS. Insert the object corresponding to a zero value at the top (or left) of your array of buttons, then put the "one" button below (or right) of it, and so on.

When the buttons are complete, the SORT operation is used to guarantee that the top/left object is in fact the first child of the parent box with the others following in order. Due to the details of object tree structure (to be discussed in the next column), this will guarantee that these objects are contiguous in the resource.

If you assign a name (say BUTTON1) to the first button, then you can initialize the correct button with the call:

 selobj(tree, BUTTON1 + field);

where field is the variable of interest.

When the dialog is complete, you can scan the radio buttons to compute the new value for the underlying variable. The encode() procedure in the download will do this. As always, remember to deselect the buttons at the end.

You can use offsets or multipliers if your variable's values don't start with zero or increment by one. If the values are irregular you may be able to use a lookup table, at the cost of additional code.


Coming up next

In the next column, I will discuss the internal structure of object trees. Then we'll use that knowledge to build a piece of code which will "walk" an entire tree and apply a function to each object. We'll apply this code to do all of the button deselects with a single call! I'll also look at handling editable text fields and discuss some ways to alter a dialog's appearance at run-time.


Dispel gremlins

An editing error caused an omission in the first installment of ST PRO GEM. The window components RTARROW and DNARROW should have been listed along with HSLIDE as the horizontal equivalents of the vertical slider components which were discussed.