Controls FORTRAN
Volume Number: 3
Issue Number: 4
Column Tag: Fortran's World
Controls from FORTRAN
By Glenn Forney, Gaithersburg, MD
Previous articles and examples in MacTutor have shown how to incorporate
features of the Macintosh user interface such as menus and windows into FORTRAN
applications. This article will show how to implement simple controls such as
buttons, checkboxes and radio buttons.
The text will identify which tool box routines to call, where to call them and why.
The example is a modified version of the shell program demo.for supplied by
MicroSoft. It will give the details of how to implement controls in FORTRAN.
Control BASICS
The three types of standard controls: buttons, checkboxes and radio buttons are
on/off switches and have control definition ID's of 0, 1 and 2. The scrollbar is a type
of control known as a dial since it can take on more than two values. It has a control
definition ID of 16. These ID's specify to the control manager what type of control to
create.
A control is either active or inactive, i.e. highlighted or unhighlighted. A control
can also be visible or invisible. Only when a control is active and visible will it
respond to the mouse.
A control has another attribute known as a part number. Each part of a control
has a unique part number. Simple controls such as buttons, check boxes and radio
buttons have only one part and hence only one part number. The scroll bar on the
other hand is more complicated and has five parts: elevator box or thumb, up arrow,
down arrow, up button and down button. Part numbers and control definition ID's for
the standard controls are listed in Figure 1.
Control Part Codes Control Definition ID
push button 10 Á0
check box 11 Á1
radio button 11 Á2
scroll bar ‡16
inUpButton 20
inDownButton 21
inPageUp 22
inPageDown 23
inThumb 129
Figure 1. Part codes and ID numbers for Standard Controls
Records Needed to Implement Controls
Control Record. A record is a Pascal data type used on the Macintosh to describe
or specify windows, dialogs, controls, events, etc. Each control that is defined has a
control record associated with it. A control record consists of 13 fields. Figure 2 lists
these fields and gives decimal offsets which can be used to access the individual fields of
the control record from FORTRAN. For example if CONTROLBEG is an INTEGER*4
variable containing the beginning address of the control record then the FORTRAN
statement LONG(CONTROLBEG+4) will give the contrlOwner field of the control
record. This field points to the window that contains the given control. Most of the
fields in the control record can be accessed through tool box calls rather than directly
with the LONG function. An exception is the first field of the control record, named
nextControl, which is a handle to the next control in the window's control list. The
fields of a control record that we will use to implement controls are: nextControl,
controlValue and contrlRfcon.
The parameter, nextControl, is a handle for the next control record in the
window's control list. So the control record for the next control in a window starts at
the address given by the FORTRAN statement:
LONG(LONG(nextControl))
When handling activate events one needs to activate or deactivate all controls in a
window. The field nextControl then serves as the link which connects all controls
belonging to a window.
The field contrlValue in the control record contains the value of the control. For
simple controls this field simply indicates whether the control is on or off. A window
can have several controls located in it. The application needs to know which one of the
window's controls was pressed. This is done with the contrlRFCon field.
Our control example with two windows
The parameter contrlRFCon is used to store information about a control. This
information could be an integer value that indexes a FORTRAN array describing the
window's controls or a handle to a more complex data structure on the heap. In general
these data structures allow the FORTRAN program to distinguish one control from
another. For example when a mouse down event occurrs in a control, there is no way
of knowing from the event record which control in the window was pressed. In the
example program we simply number the controls 1, 2, 3, 4, 5 and set the
controlRfCon field to these values.
Window Record. A second record needed to handle controls is the window record.
The controlList field of this record gives a handle to the first control in the window's
control list. So if WINDOWBEG is the address of the beginning of a window record then
LONG(WINDOWBEG+Z'8C') is the handle to the control record of the first control in a
window. If there are no controls in a window then this value is zero. The value Z'8C'
is the hex value of the offset from the beginning of a window record. It indicates the
location of the controlList field in the window record. For an illustration of this
process see the code segment below the 'CASE(ACTIVATEEVT)' statement in the main
event loop of the example program.
Event Record. Another record of interest when handling controls is the event
record. Some examples of possible events are: the mouse is clicked ( a mouse down
event), a window is drawn or erased ( an activate event), a window is covered or
uncovered ( an update event). The individual fields of an event record are usually
accessed with FORTRAN equivalence statements. The compiler does the work of
computing offsets to individual fields of the event record rather than the programmer.
The event record gives a description of the state of the Macintosh when the event
occurred, e.g. the time in ticks since startup, the mouse location in global coordinates,
state of the keyboard, etc. We are interested in the fields: where, modifiers and
message. When a mouse down event occurrs in the content region of a window, the
where field, after converting to local coordinates with GLOBALTOLOCAL, indicates the
mouse down location in the window. For an activate event, the modifiers field indicates
whether a window should be activated or deactivated. For an update or activate event,
the message field gives a pointer to the window where an update or activate event
occurred. So when implementing controls we need to access these fields of the event
record to determine how the program should respond to an event.
All of the tool box routines that we want to use are documented in Inside
Macintosh. To use these routines from FORTRAN one needs to know how to use
toolbx.sub to implement PASCAL calls in FORTRAN. Figure 3 gives a list of common
PASCAL data types and their FORTRAN counter parts. This along with the example
program should aid in implementing other undocumented tool box routines.
Creating Controls
There are several methods for creating controls in FORTRAN. The first method is
to create it in FORTRAN using the tool box routine NEWCONTROL. Another method of
creating a control is to read in a control resource that was compiled with RMAKER
using the tool box routine GETNEWCONTROL. The programmer needs to fill in the
resource id number and the pointer to the window the control in which will be. As in
NEWCONTROL the routine GETNEWCONTROL returns a handle to the newly created
control. A third method is to read in a list of dialog items from a resource with the
routine GETNEWDIALOG.
Controls and Events
Activate Event. When implementing a control in an application one needs to be
concerned with three events. These are activate, update and mouse down events. If a
user has controls in a window where an activate event occurs then (s)he is responsible
for highlighting or unhighlighting the control. This is done with the tool box routine
HILITECONTROL. The programmer passes a control handle and a highlight code of 0 or
255 to HILITECONTROL where 0 means highlight (the control is to become active) and
255 means unhighlight. To know whether to highlight or unhighlight, test the first bit
of the modifiers field in the event record. If the first bit is 1 then the window is being
activated so the control needs to be highlighted, otherwise the bit is 0 and the control
needs to be unhighlighted. A code segment to illustrate this is listed under the
CASE(ACTIVATEEVT) section of the example program.
Update Event. The next event of concern is the update event. An update event is
passed to your application when the system determines that a window's contents have
been changed and hence need to be updated. To handle an update event in a window that
contains one or more controls, one simply redraws the controls by passing a window
pointer to the tool box routine DRAWCONTROLS. The window manager redraws the
window, the programmer however is responsible for redrawing the controls and any
other contents in the window. Any output to the window while updating must be
bracketted by calls to BEGINUPDATE and ENDUPDATE. BEGINUPDATE clears the update
region of the window. If these routines are not called then after the controls are
drawn the system still thinks that the window needs to be updated so another update
event is generated and the controls are redrawn and so on. The result is that the
Macintosh "locks up" and the window contents exhibit a rapid flickering.
Mouse Down Event. There are several types of mouse down events that can occur.
For example the mouse can be clicked in a menu bar, in a system window or in the
content region of an application window. The mouse down event in which we are
interested is when the mouse is detected in the content region of an application window.
In this case we need to know if it was clicked in a control.
Each control belongs to a particular window. The coordinates used to express the
location of the control inside the window must be in the window's local coordinates.
When a mouse down event occurs, the mouse location is given in global coordinates by
the 'where' field of the event record. So the mouse position must be converted to local
coordinates using the tool box routine GLOBALTOLOCAL. Next the application needs to
call the routine FINDCONTROL to determine if the mouse was pressed in a visible,
active control. FINDCONTROL is passed the mouse location in local coordinates and a
window pointer. It returns a handle to the selected control and the part number where
the mouse was down if the control was visible and active, otherwise FINDCONTROL
returns a zero.
If FINDCONTROL determines that the mouse was clicked in a control then the
application calls TRACKCONTROL to see if the mouse is still in the control when the
mouse button is released. If this is so then TRACKCONTROL returns the part number of
the control. It is up to the application then, to get the value of the control with
GETCVALUE, set it to a new value with SETCVALUE and perform the action that the
application requires. For example if an application window has a scroll bar that
contains text and the control is set to some new value then the control manager will
redraw the control. The application is responsible for redrawing the text to reflect the
new value of the control.
All of these ideas are put together in the example program that follows. It is
relatively short but it does exhibit all of the features necessary to implement a simple
control.
Concluding Remarks
The tool box routine TRACKCONTROL has a calling argument named procPTR. The
argument procPTR is a pointer to a procedure that TRACKCONTROL calls periodically
while the mouse is down in a control. This is how auto scrolling is done. In standard
FORTRAN it is not clear how you would pass a pointer to a FORTRAN routine through
the calling list of TRACKCONTROL or any other tool box routine. Absoft has solved this
problem by writing an Assembly language "glue" subroutine that returns the pointer
to a FORTRAN subroutine. A future article is needed that discusses this glue routine for
scrolling text windows.
[Implementation notes: In compiling and executing this example, we had the usual
pains with the latest Microsoft Fortran 2.1 version. For one thing, the include
statement for the menu.inc file produced an error! It seems someone either left out the
last line in that include file or forgot to close the line with a ")". We wonder how many
other include files also have bugs! Once over that problem, we had to get the include
statements to specify the complete path name of the files. We got a lot of compile
errors on this one. We finally got it to work after re-typing the path name in by hand,
but never did really figure out what the compiler didn't like. Then came the wonderful
HFS brain damaged linker! It seems the linker doesn't like complete path names. We
had to specify a partial path name from the current level where the linker was located
down to our file. Guess the linker can only look down, not up. At any rate, we finally got
a compilation and linkage. The linker script produces an application file. And the
compiler produces an application file! And the resource compiler also produces an
application file, so now you have three application files floating about, only one of
which is the "real" application! So be careful. We called the output of the compiler,
"control example apl", then called the linker output "control" and finally, the RMaker
output, "RealControl" since it was the "real" one. As you can guess, we compiled, then
linked in the run time library, and finally merged the resources in with RMaker to
produce the final application. -Ed]
PROGRAM CONTROLEXAMPLE
*
* THIS PROGRAM IMPLEMENTS SIMPLE CONTROLS IN FORTRAN
*
* AUTHOR: GLENN FORNEY
* DATE: 8/86
IMPLICIT NONE ! HELPS KEEP US OUT OF TROUBLE
*
* THE FOLLOWING INCLUDE FILES ARE INCLUDED WITH MS FORTRAN
* YOU SHOULD CHANGE THE PATHNAMES TO MATCH YOUR SETUP
*
include XP40-6:MacFortran:IncludeFiles: window.inc
include XP40-6:MacFortran:IncludeFiles: dialog.inc
include XP40-6:MacFortran:IncludeFiles: event.inc
include XP40-6:MacFortran:IncludeFiles:menu.inc
include XP40-6:MacFortran:IncludeFiles:control.inc
include XP40-6:MacFortran:IncludeFiles:quickdraw.inc
include XP40-6:MacFortran:IncludeFiles:misc.inc
*
* DECLARATIONS
*
INTEGER*4 TOOLBX ! THE TOOL BOX INTERFACE
INTEGER*4 WINDOW ! GENERAL PURPOSE POINTER
INTEGER*2 RECT(4) ! RECTANGLE COORDINATES
INTEGER*4 CONTROL_WINDOW ! POINTER TO THE CONTROL WINDOW
INTEGER DUMMYHANDLE ! HANDLE TO DUMMY WINDOW
INTEGER MENUHANDLE ! HANDLE TO MENUS
*
* WCONTRLLIST IS AN OFFSET THAT GIVES A
* HANDLE TO THE FIRST CONTROL IN A WINDOW
* HEX 8C = DECIMAL 140, SO WCONTROLLIST BEGINS AT THE
* 140'TH BYTE IN THE WINDOW RECORD
*
INTEGER WCONTROLLIST
PARAMETER (WCONTROLLIST = Z'8C')
*
* DECLARATIONS FOR VARIOUS EVENTS AND MOUSE DOWN LOCATIONS
*
INTEGER ACTIVATE,CONTROL_HAN,SAVEPORT
INTEGER MOUSEDOWN,UPDATEEVT,ACTIVATEEVT
PARAMETER (MOUSEDOWN=1,UPDATEEVT=6,ACTIVATEEVT=8)