Window Ports
Volume Number: | | 1
|
Issue Number: | | 5
|
Column Tag: | | PASCAL PROCEDURES
|
Windows provide a front end to ports
By Chris Derossi
In the last issue of MacTutor, we introduced briefly the concept of graph ports and how they can make both programming and usage easier and more intuitive. However, the way that we used the ports, the programmer had to keep track of everything. It is fairly obvious that there will be several features that you, as a programmer, would want to associate with ports frequently if not all the time. It is for this reason that windows exist.
Windows provide a front end to ports, making them more easy to use and providing you with several options and features. The Window Manager in the Macintosh Toolbox takes care of the tedious tasks for you. For example, in our last program, we had to do everything with the port, including erasing it to white and drawing the border around the port. It is simple things like this, that you need for every port, that the Window Manager handles.
In addition to taking care of the low level drawing, windows also provide you with more complex options. Im sure that youve seen the title bar on document windows, and the scroll bar controls are standard, too. The Window Manager also sets the windows coordinate system to local coordinates, with the point (0,0) at the upper left corner of the window. Also handled for you is the setting of the various regions. And, unlike ports, windows can handle overlapping and res- toration of the window contents.
Lets back up a moment and take a look at the various different window kinds. There are four standard windows in the Macintosh, and you may create your own.
The four standard windows are shown in figure #1. Each type has an associated function on the Mac. Window type #0 is for documents, folders, and disks. Window type #1 is the normal dialog and alert box window, with the shadow border.
Type #2 is like type #1, but without a border. Type #16 is usually found as the window for a desk accessory. Types #0 and #16 have titles, while the other two do not.
Fig. 1
For each type of window, the Window Manager handles drawing of the border and title bar. To do this, each window must have associated with it information about the type of window, and the title for that window. In fact, the magic about windows is just that: associating various things with a particular window.
Of course, the heart of the window is a GrafPort. Each window is its own port, and is unrelated to other windows. The rules for windows are the same as those for ports discussed last time. As you might have guessed, some of the other things associated with each window include window type and title string. There are also several status flags that are kept with each window that keep track of things like whether or not the window is visible, and if it is if its hilited.
Figure #2 shows a rough outline of a window record. Some of the fields are not shown, and many other are grouped under the heading Window Stuff. We will worry about these at a later time. The very first thing in the record is the GrafPort. After that comes most of the window flags and status variables. The windowDefProc is a handle to the procedure that draws the window. This is the procedure that handles the title bar and the border. The four standard windows each have a DefProc. You may create your own window definition procedure and have the Window Manager use it to draw the window.
The titleHandle is a handle to a string in memory. This is how the title is related to the window. Since controls are also associated with windows, a handle to a list of controls is also included in the record. More about this at a later time.
The next field is very important. The field nextWindow is a pointer to the next window record in a linked-list type data structure. By using this field, the Window Manager knows which window to hilite and make active if you get rid of the currently active one. Also, by using this sceme, the Window Manager needs to keep track of only one Window Record, and follow the pointer chain till it finds the one it wants.
Finally there is a field called refCon. This field has nothing at all to do with the window itself. Apple put that field there for your own use. You may have data associated with one of your windows, or a value assoctiated with it. You may use this field as a pointer, handle, integer, or long integer. (Some type conversion may be needed, but you have enough bytes for any of those constructs.)
As with ports and other things, there are toolbox procedures and functions that create, destroy, modify, and control windows. The usual parameter passed to these routines is a WindowPtr, a pointer that points directly to the window record. In many cases, the Window Manager is looking for the data type GrafPtr instead of WindowPtr. This works because there is an entire GrafPort at the beginning of the window record.
Inline Trap Calls From Pascal
Well come back to windows in a moment; right now lets discuss the method for getting to toolbox routines that dont have a MacPascal interface. (Window routines are in this catagory.)
MacPascal has some built in procedures and functions that call the Macintosh ROM, and even more routines in separate libraries. However, these routines fall very short of covering the entire set of toolbox utilities. For example, there is a toolbox function called OpenWindow, but there is no library available for MacPascal where this function is defined. Normally, there would be no way to interface to this and other similar routines, but realizing that doing so would be a common and important need, MacPascal has a short-cut set of routines known as InLines.
The InLine procedures and functions allow direct access to the Macintosh internals. Macintosh ROM routines are accessed by a thing called a trap vector. When the 68000 microprocessor comes upon an instruction that begins with a certain series of bits (1010, or $A in hex), it looks up an address in a RAM table and jumps to it without question. The Mac ROM and the System program are resposible for setting up this table. It is through this method that routines in the ROM are accessed.
[NOTE: The trap mechanism has several advantages. First, as a programmer, you dont have to know the exact location of the routines you want to use. This allows the RAM table to change, and allows bugs to be repaired by replacing the ROM routine with a better one in RAM and updating the table. Also, you dont need to use the bytes neccessary for a subroutine call. Instead, you need only the two-byte trap value. Lastly, the RAM table can be modified to create several nice effects. For example, you can intercept ALL of the traps to find out whats happing when debugging. Also, you can replace the ROM routines with your own routines that are better or different. All in all, trap vectors provide for a more verstile computer.]
The use of the InLine procedure call is fairly straightforward. The only mandatory parameter is the trap vector value. Each Mac ROM routine has a different trap vector value. You need to know what the trap vector is for the routine you wish to use. (Some of the window manager and quickdraw vectors are included this month. Look for a complete list in a later issue.)
Do Your Own Type Checking!
Many of the procedures require parameters. You may simply pass these parameters after the trap value. You may place as many parameters as you need in the call, and the parameters may be of ANY type. The InLine calls short cicuit the normal type checking of MacPascal. For this reason, you should know exactly what you are doing when using an InLine call. If you pass a bad set of parameters, your Mac could crash and you could lose all data in memory and on disk!
There are two methods for passing parameters, by value and by reference. Simple data types (i.e. predefined) are passed by value unless they are VAR parameters. The simple data types include Integer, LongInt, Boolean, Char, Pointer, and Handle. All other data types are complex. Complex data types and VAR parameters are passed by reference. When a parameter is passed by value, the actual value of the parameter is placed on the stack. When passing by reference, a POINTER to the data is placed on the stack.
Since the InLine procedures are dumb, you must control what happens. Passed by value parameters can be passed normally, and complex types can be passed normally too as MacPascal knows to place a pointer on the stack. When you have a simple data type that is supposed to be a VAR parameter, you MUST force MacPascal to pass a pointer, not the data itself. To do this, use the @ function, which returns a pointer to the argument. For example, suppose that we have a variable MyNum which is of type integer. To pass the value of MyNum, you would pass MyNum itself. If you needed a VAR parameter, you would have to pass @MyNum.
The InLine call for procedures is InLineP(Trap, parm1, parm2, ...); You may have zero one or more parameters. There are three InLine calls for functions. Since InLine does not know what size value is to be passed back as the return value, a different call is provided for each type. When a value of size one byte is to be returned, use BInLineF(Trap, parms...). For two-byte long results, use WInLineF(Trap, parms...). W is for Word which is two bytes. L is for LongWord which is four bytes, which is the size of longints, pointers, and handles, so use LInLineF(Trap,parms...).
Freedom From Large Libraries
Using InLine calls does not require any MacPascal libraries; InLine calls are built directly in to MacPascal. There is nothing to stop you from using InLine calls to the ROM even when equivalent routines already exist. In fact, there is a reson to do so. When you need any one procedure or function from a library, the entire library is loaded into memory. If you only need one function, most of the RAM being used by the other functions is wasted. By using a single InLine call instead of loading the entire library, you can save a significant amount of memory for your own data.
[NOTE: ANY single reference to the simple QuickDraw functions causes MacPascal to load the library QuickDraw1. In other words, even though you didnt have to do a uses QuickDraw1, it still happens, and you might consider replacing the calls with InLines.]
Now that you know how to access the Window Manager routines, lets look at the sample program. This month, the sample program is just a vehicle for the programs sole procedure, Message. This procedure stands by itself, does not require any librarys, and may be ported to your own program. (With the exception of the type MsgArray, which must be declared before the procedure Message is.)
The procedure Message takes four inputs. First and second are the message you wish displayed. The first parameter is the number if lines of the array to use. The second is the array itself. Other methods could have been used, but well talk about that at the end. The next parameter is the window type, and can have values 0, 1, 2, or 16, corresponding to the window types in figure #1. Finally, a delay value is provided if you want to slow the output for effect.
DISPOSE should be Disposed of!
To create a window, it is neccessary to provide the space for the window record. One way to do this would be to declare the entire window record type, but that is quite large. Instead, the size of the record is known to be 156 bytes. By declaring the record as an array of 78 integers, we achieve the same size data structure. Also, instead of using the built in NEW procedure which allocates the memory, we create the variable ourselves. The reason for this is that the DISPOSE procedure that deallocates the memory doesnt work, and the memory never becomes free. Instead, we declare the variable, and its memory is freed when the procedure terminates.
AVOID A QUICKDRAW1 LOAD
The InLine call to SetRect creates the windows rectangle. Notice that an InLine call is used to avoid loading QuickDraw1. Also, Rect is defined separately as well. (ANY reference to the data types in QuickDraw1 causes QuickDraw1 to be loaded.) Then, the InLine call for OpenWindow is executed. The Window Manager takes care of setting up the grafport, drawing the window, erasing it to white, and drawing the title bar if it has one. When this call is complete, the window will be on the screen.
Normally, this would cause the window to become the current grafport, but because MacPascal handles graphics and text in different windows, and therefore keeps changing the current port, it is a good idea to do a SetPort call. Once the window is set to be the current port, a simple loop calls DrawChar to place each character in the window.
When the loop terminates, the procedure waits for a mouse press and release. The call to the ROM routine Button is built into MacPascal, and no libraries need to be read. The procedure then closes the window and terminates. The Window Manager erases the window.
As an exercise, you may wish to modify the Message procedure directly. There is nothing to stop you from doing more elaborate drawing in the window. You might want to create your own alert procedures that draw bombs or other icons in the window. Additionally, you may want to pass the windows title and rectangle to Message. You could also delete the delay if you dont want it.
That covers the basic introduction to windows. In the next issue, well talk about how to handle multiple windows, even overlapping ones. Also, well discuss the use of controls in general, and controls in windows. Up till now, weve limited ouselves to Button and GetMouse. The use of controls will greatly enhance our ability to receive input from the user. As you can see, windows with all their associated features provide you with a very powerul set of tool. Ciao.
program Window_Example;
{ This is the sample program for the}
{ April issue of MacTutor Magazine.}
{ The main part of this program is only a}
{ front end for the procedure}
{ called Message. You can take the}
{ message procedure into your own}
{ programs as it stands.}
{ by -- Chris Derossi}
const
MaxLines = 5;
type
MsgArray =
array[1..MaxLines] of Str255;
var
MyMsg : MsgArray;
procedure Message
(Lines : INTEGER;
Msg : MsgArray;
WindKind, Delay : INTEGER);
{ This procedure is the heart of this}
{ months sample program. It will}
{ open a window on the desktop, display}
{ your message using the delay,}
{ and then wait for a press of the mouse}
{ button before it closes the}
{ window and returns.}
const
{Window Manager Traps}
NewWindow = $A913;
CloseWindow = $A92D;
{QuickDraw Traps}
SetRect = $A8A7;
SetPort = $A873;
MoveTo = $A893;
DrawChar = $A883;
type
Rect : array[1..4] of INTEGER;
WindowRecord = array[1..78] of Integer;
WindowPtr = ^WindowRecord;
var
Count, X, Index : INTEGER;
Dummy : LongInt;
TheWindow : WindowPtr;
WindowStorage : WindowRecord;
BoundsRect : Rect;
begin
{ Initialize memory, and open a new}
{ window. }
TheWindow := @WindowStorage;
InLineP(SetRect, @BoundsRect,
40, 50, 390, 140);
Dummy := LInLineF(NewWindow,
TheWindow,
BoundsRect,
Message Window ,
TRUE,
WindKind,
Pointer(-1),
FALSE,
0 + 0);
InLineP(SetPort, TheWindow);
{ Output the message, character by}
{ character. }
for Index := 1 to Lines do
begin
X := Index * 15;
InLineP(MoveTo, 5, X);
if LENGTH(Msg[Index]) > 0 then
for Count := 1 to LENGTH(Msg[Index]) do
begin
for X := 1 to Delay do
; {Nothing, just loop for a pause}
InLineP(DrawChar, Msg[Index][Count]);
end;
end;
{ Wait for a button press and release.}
repeat
until Button;
repeat
until not Button;
{ Get rid of the window.}
InLineP(CloseWindow, TheWindow);
end;
begin {Window_Example}
HideAll;
MyMsg[1] := ;
MyMsg[2] := Your example program that demonstrates windows;
MyMsg[3] := has now begun.;
Message(3, MyMsg, 16, 0);
MyMsg[1] := You can produce your own error messages and alert;
MyMsg[2] := boxes in your MacPascal programs.;
Message(2, MyMsg, 1, 75);
MyMsg[1] := Pause and prompt for a press of the mouse button.;
Message(1, MyMsg, 2, 75);
MyMsg[1] := You can also post brief instructions for your;
MyMsg[2] := users at any time while the program is running.;
Message(2, MyMsg, 0, 100);
MyMsg[1] := ;
MyMsg[2] := ;
MyMsg[3] := ;
MyMsg[4] := This demonstration program is now over.;
Message(4, MyMsg, 16, 0);
end.