Apple IIgs #42
Custom Windows

Written by Dan Oliver & Keith Rollin (November 1988)

This Technical Note describes custom windows which are now supported with Window Manager version 2.2. This Note supersedes all prior documentation on custom windows.


With Window Manager version 2.2 or later, which is available on Apple IIGS System Disk 3.2 and later, you may now define your own type of window or window shape, such as a round or hexagonal window. You also may define a window which performs tasks that would normally be handled by an application.

To define your own type of window, a custom window, you must write a routine that performs some window functions. This routine is a window definition procedure (defProc), and in this case it is a custom window defProc. When the Window Manager needs to do something window specific, it calls your defProc.

The window defProc is a good part of the Window Manager, and writing one is not an easy task. A window defProc must perform complicated tasks that are very dependent on the state of the machine, and it must be very careful not to disturb the state of the machine. One of the problems in writing a defProc is knowing when it can do something and when it cannot. It is almost impossible to document all of the combinations of calls that you can or cannot make from one part or another of the defProc, and even if all cases were found, the resulting document would read like something from an obscure government bureau and probably be even harder to understand.

Now that you know writing a defProc is tough, here's how to make things as easy as possible. Try to understand how the system interacts with the defProc and work with the system. For example, a defProc is called to hit test window parts when the user presses the mouse button. The Window Manager will pass that part back to the defProc to perform drawing while the Window Manager is tracking the pressed button. The defProc could keep control when asked to hit test and perform the tracking itself, but since this is not how the system is designed to work, your defProc will be hard to write, may not ever work correctly, and may break in future versions of the Window Manager. Try to stay on the path outlined in this Technical Note. Also understand that the interface to definition procedures is as general as possible to allow them to perform tasks which are as yet unknown. To allow for this future growth, the outlined path is not always a clear path.

Another way to make things easier is to write conservative code. Do not assume things like the data bank being set to something nice when the defProc is called or the caller restoring the direct page pointer upon return if you have changed it. Use caution. A defProc can be very difficult to debug because it is not very linear and can be called when you least expect.

Interaction Between the Window Manager and TaskMaster

The Window Manager and TaskMaster actually do much less than many people think since window definition procedures perform most of the tasks. The definition procedures handle such things as title bars, information bars, and scroll bars, while the Window Manager and TaskMaster support these things by passing requests to the defProc in standard ways. The Window Manager knows that windows have some shape, overlap, may contain parts, may be invisible, and are created and deleted, but it does not know much else. TaskMaster knows to call GetNextEvent and performs some tasks, but much of what many people consider TaskMaster is contained in the standard document window defProc. In addition to the list mentioned above, the defProc handles calling TrackGoAway and scrolling the content. The remainder of this Note describes what is expected of a defProc and when.

Telling the Window Manager About Your Window

You tell the Window Manager about your custom window when NewWindow creates it. Instead of passing the parameter list defined in NewWindow, you pass a pointer to a custom window parameter list. A custom window parameter list is defined as follows:

paramID (WORD)
ID of parameter list, zero for custom.
newDefProc (LONG)
Address of your custom defProc.
newData (BYTE[n]
Additional data defined by your defProc.

NewWindow checks the paramID field and calls your defProc with the pointer to the parameter list. See the wNew operation under Calling the Custom DefProc for more information.

Once NewWindow creates the window, the Window Manager will always know that it is defined by your defProc.

Calling the Custom defProc

A window defProc is called with the following items on the stack:


    16 |result                     | LONG - result returned to Window Manager,
       |___________________________|        defined by each operation
    14 |windGlobals                | LONG - pointer to Window Globals (defined below)
       |___________________________|
    12 |OperationCode|               WORD - operation number to be performed
       |_____________|_____________
     8 |theWindow                  | LONG - pointer to window's record
       |___________________________|
     4 |param                      | LONG - pointer to additional parameter
       |___________________________|        defined by each operation
     1 | RTL address        |        BYTE[3] - long return address
       |____________________|______
       |                           | <-- Stack Pointer
       |                           |

              Figure 1 - Stack Prior to Calling a Window defProc

The defProc must return with the carry flag clear if there was no error or with the carry flag set and the y register set with an error code if there was an error.

Window globals (windGlobals) is a pointer to a table of variables which the Window Manager maintains for use by the defProc. The table is defined as follows:

lineW (WORD)
Width of vertical lines (size depends on video mode).
titleHeight (WORD)
Height of a standard title bar.
titleYPos (WORD)
Y offset for the title (in system font) to center in a standard title bar.
closeHeight (WORD)
Height of the close box icon.
closeWidth (WORD)
Width of the close box icon.
defWindClr (LONG)
Pointer to the default window color table.
windIconFont (LONG)
Handle of the current window icon font.
screenMode (WORD)
TRUE if 640 mode, FALSE if 320 mode.
pattern (BYTE[32])
Temporary pattern buffer.
callerDpage (WORD)
Direct page pointer of the last caller to TaskMaster.
callerDataB (WORD)Data bank of the last caller to TaskMaster (bank in both bytes).

Operation numbers are as follows (each operation is described later in its own section):

wDraw (0)
Draw the window's frame.
wHit (1)
Tell in what region the mouse button was pressed.
wCalcRgns (2)
Calculate wStrucRgn and wContRgn.
wNew (3)
Complete the creation of a window.
wDispose (4)
Complete the disposal of a window.
wGetDrag (5)
Return address that will draw the outline of the window while dragging.
wGrowFrame (6)
Draw the outline of a window being resized.
wRecSize (7)
Return size of the additional space needed in the window record.
wPosition (8)
Return RECT that is the window's portRect.
wBehind (9)
Return where the window should be placed in the window list.
wCallDefProc (10)
Generic call to a defProc, defined by the defProc.

Operation 0: wDraw

The wDraw operation draws the window's frame and is only called for visible windows. This operation draws in local coordinates in the current GrafPort, which is the Window Manager's GrafPort. When the drawing is finished, the only states of the GrafPort that may have changed are the pen pattern, the fill pattern, and the pen size, as all other states must be the same as when the defProc was called. This means that if you change the font to print some text, you must save and restore the original font. For the pen, PenNormal will restore the pen to an acceptable state.

Param is defined as follows:

Bit 31
1 to highlight the indicated part, 0 to unhighlight.
Bits 0-30
The part to draw (either highlighted or unhighlighted):
0: Draw the window's entire frame, including any frame controls and the items listed below. Note that you should check the window's fHilited flag to determine how to draw the frame.
1: Draw the go-away region.
2: Draw the zoom region.
3: Draw the information bar.

Result returned must be zero and the carry flag must be clear.

The Window Manager will draw the content.

Need to Redraw Your Window?

If your custom window defProc gets called to change some item in its window record (see wCallDefProc below), you may want to redraw your window. For instance, if your application makes a SetWTitle call, you would want to draw the name of the new title on the screen.

The routine wCallDefProc can call the wDraw routine to do this drawing. However, it should bracket the calls to wDraw with two Window Manager calls that save and restore some internal variables:


    StartFrameDrawing    $5A0E
    PUSH:LONG            Pointer to the window record (not the GrafPort)

This call does the setup for drawing a window frame and is only called by a window definition procedure before drawing the frame. You should call EndFrameDrawing when finished drawing.


    EndFrameDrawing      $5B0E
    No input or output

This call restores the Window Manager variables after a call to StartFrameDrawing and is only called by a window definition procedure after drawing a window frame.

Operation 1: wHit

The wHit operation is called to hit test the window's frame. Given a set of screen coordinates, this operation should return what part, if any, of the window is at that coordinate. This operation is only called for visible windows. The current port will be that of the Window Manager and the window frame will be in local coordinates.

Param is defined as:

Bits 0-15
Vertical (Y) coordinate in local coordinates.
Bits 16-31
Horizontal (X) coordinate in local coordinates.

Result returned must be one of the following values and the carry flag must be clear:

0: wNoHit
Not on the window at all.
20: wInDrag
Coordinates are in the window's drag region (title bar).
21: wInGrow
Coordinates are in the window's grow region (size box).
22: wInGoAway
Coordinates are in the window's go-away region (close box).
23: wInZoom
Coordinates are in the window's zoom region (zoom box).
24: wInInfo
Coordinates are in the window's information bar.
27: wInFrame
Coordinates are in the window, but not in any of the other areas.
xx:
Any code the application can handle (bit 15 is reserved for theWindow Manager)

Operation 2: wCalcRgns

The wCalcRgns operation, which is called only for visible windows, is used to calculate the window's entire region (frame plus content called StrucRgn) and just its content region (called ContRgn). Both regions must be set to global coordinates, and both will already be allocated with their handles stored in the window record's wStrucRgn and wContRgn fields.

Use the portRect and the boundsRect of the window's GrafPort to calculate these two regions. The port will have been set from the information passed to NewWindow along with any size changes. A method for obtaining the global RECT of the content is given below. Refer to the QuickDraw II chapter in the Apple IIGS Toolbox Reference for a full description of ports. When calculating the regions, do not change the clip region (ClipRgn) or the visible region (VisRgn) of the GrafPort.

Param is not defined and should not be used.

Result returned must be zero and the carry flag must be clear.


    IN:    window = pointer to window record.
    OUT:    rect = global RECT of window's content.
    
        ldy    #wPort+portRect+y1
        lda    [<window],y
        ldy    #wPort+portInfo+boundsRect+y1
        sec
        sbc    [<window],y
        sta    <rect+y1
    ;
        ldy    #wPort+portRect+x1
        lda    [<window],y
        ldy    #wPort+portInfo+boundsRect+x1
        sec
        sbc    [<window],y
        sta    <rect+x1
    ;
        ldy    #wPort+portRect+y2
        lda    [<window],y
        ldy    #wPort+portInfo+boundsRect+y1
        sec
        sbc    [<window],y
        sta    <rect+y2
    ;
        ldy    #wPort+portRect+x2
        lda    [<window],y
        ldy    #wPort+portInfo+boundsRect+x1
        sec
        sbc    [<window],y
        sta    <rect+x2

Although there are other ways to obtain the global RECT of the content, this example gives the correct method. You should never rely on the top and left side of the portRect being zero.

Operation 3: wNew

The wNew operation is called to perform any additional initialization that may be required for a custom window. The following items are already done for the window:

Param is a pointer to the parameter list pointer which was passed to NewWindow.

Result returned must be zero and the carry flag must be clear.

Operation 4: wDispose

The wDispose operation is called to perform any additional disposal that may be required of a custom window. This operation is called before the Window Manager performs any disposal actions on the window.

Param is not defined and should not be used.

Result should be FALSE to continue disposal or TRUE to abort the disposal. In either case, the carry flag should be clear. Returning TRUE would be very unusual and should be carefully thought out. After returning FALSE, the Window Manager will erase the window, remove the window from the Window Manager's window list, free any controls in the window's wControls and wFrameCtl lists, free the handles in the wStrucRgn, wContRgn and wUpdateRgn fields, close the window's GrafPort, and free its record if it is allocated (see the wFrame field).

Operation 5: wGetDrag

The wGetDrag operation is called to get the address of a routine that will draw an outline of the window.

Param is not defined and should not be used.

Result returned must be the address of a frame outline routine or zero for a default frame; the default frame is the bounds RECT of the strucRgn. The frame outline routine is called from DragRect with dragRectPtr set to the bounds RECT of the strucRgn. Your routine is called with the following parameters:


    PUSH:WORD - delta X
    PUSH:WORD - delta Y
    PUSH:BYTE[3] - return address

Your routine should draw or erase the outline of the object in its new position using the passed deltas. You have several different methods of determining whether to erase or draw and how to compute the position of the object, the easiest method being to draw the outline using XOR mode. The first time your routine is called, you draw. The next time your routine is called, you erase. Your routine should draw in the current port. The current pen pattern will be the pattern pointed to by dragPatternPtr from DragRect and the pen mode is XOR.

You also need to know where to draw the outline. One way is to offset the starting RECT (dragRectPtr) by the given deltas. You should make a copy of the bounds RECT of the strucRgn when wGetDrag is called. Modify that rectangle with the deltas to obtain the rectangle to frame.

Operation 6: wGrowFrame

The wGrowFrame operation is called to draw an outline of the window when the window is being resized.

This operation should use the current port, pen pattern, and pen mode. The frame should be drawn with only the following QuickDraw II calls: Line, LineTo, FrameRect, FrameRgn, FramePoly, FrameOval, FrameRRect, and FrameArc (the Invert equivalents to Frame could also be used). You want to use the current GrafPort setting with only certain QuickDraw II calls since this routine will be called an even number of times; the first time it is called to draw the frame and the next time to erase that which it drew the first time. If it needs to use QuickDraw II calls other than those listed above, this operation handler could keep track of odd and even calls to know whether to draw or erase the frame.

Param is a pointer to the following parameter list:

newSize (RECT)
Rectangle that defines the new size.
drawFlag (WORD)
TRUE to draw the frame, FALSE to erase.
startRect (RECT)
Bounds of wStrucRgn when dragging started.
deltaY (WORD)
Vertical movement since starting to drag (signed).
deltaX (WORD)
Horizontal movement since starting to drag (signed).

Result should be:


     ____________...____________________________
    | 31| 30| 29|...| 6 | 5 | 4 | 3 | 2 | 1 | 0 |
     ____________...____________________________
                                          |   |
              TRUE if newSize RECT has been   +-- TRUE if frame drawn,
              recomputed, FALSE if newSize        FALSE to draw default frame.
              RECT OK.

The Window Manager assumes that the frame of the grow outline is the same as the bounds of the window's wStrucRgn. This RECT is stored in the startRect of the parameter list and does not change through out the dragging. The next assumption is that the window grows from the lower right corner. As the cursor moves, the lower right corner of the RECT in newSize changes. However, if these assumptions are not correct for a custom window they can be overridden by changing the RECT in newSize (by using startRect or the window's record and the deltas) and returning TRUE for bit 1 in Result. The carry flag should return clear.

Operation 7: wRecSize

The wRecSize operation is called to ask how large a window record should be allocated.

Note: The window pointer passed in theWindow is not valid for this call.

Param is the parameter list pointer that is passed to NewWindow.

Result is the number of additional bytes required in the window record. The standard window record header will always be allocated.

Example:

If your custom window needs a one word field in the window record for your own use you would return 2 in Result. The Window Manager takes Result and adds to it the size of the standard record header of 212 bytes and allocates a window record that is 214 bytes long in this case. Your one word field is at the end of the standard window record header with an offset of 212 bytes.

If there is some error, return the carry flag set with an error code in the y register, which will cause NewWindow to abort and return the error code to the application which called it. If there is no error, return the carry flag clear.

Window Record Already Allocated?

If the window record is already allocated then Result should be the pointer to the window record with bit 31 of the pointer set to TRUE. Generally, window records are allocated (refer to Window Record Definition at the end of this Note for more information about window records).

Operation 8: wPosition

Param is the parameter list pointer that is passed to NewWindow.

Result is a pointer to the RECT that will be the window's portRect, and you should return the carry flag clear.

Operation 9: wBehind

Param is the parameter list pointer that is passed to NewWindow.

Result is where the window should be placed in the window list. A long $FFFFFFFF means insert the window as the top window while a long $00000000 means to insert it as the bottom window. Any other value is a pointer to the window behind which this window should be placed. You should return the carry flag clear.

Operation 10: wCallDefProc

WCallDefProc is a generic call to the defProc that is defined by the defProc. With this call a window defProc can define many special functions.

The input to the defProc is:


    param = pointer to the following parameter table:

dRequest (WORD)
Requested operation number.
paramID (WORD)
Parameter block type:
$0000-$7FFF reserved by system ($0000 defined below).
$8000-$FFFF reserved for custom defProcs.
newParam (BYTE[n])
New parameter field used by some operations.

The paramID field defines dRequest, which in turn defines newParam and the result of the wCallDefProc call. You can think of dRequest as the operation number passed to the defProc. Here is an example of how the paramID defines dRequest: if paramID is zero, dRequest 3 is defined as wSetPage (defined below); but if paramID is $8345 (or any number other than zero), dRequest 3 could be defined as something entirely different.

The following dRequest values are defined for wCallDefProc operations with a paramID of zero. Your defProc should check for handling only these codes. In the future, codes 34 and greater may be defined, and your defProc should know not to handle them.

0: wSetOrgMask

newParam
WORD: window's origin mask.
result
None.

Called when SetOriginMask is called.

1: wSetMaxGrow

newParam
WORD: maximum window height.
WORD: maximum window width.
result
None.

Called when SetMaxGrow is called.

2: wSetScroll

newParam
WORD: number of pixels to scroll when arrow is selected.
result
None.

Called when SetScroll is called.

3: wSetPage

newParam
WORD: pixels to scroll when page region is selected.
result
None.

Called when SetPage is called.

4: wSetInfoRefCon

newParam
LONG: value passed to info bar draw routine app's use only).
result
None.

Called when SetInfoRefCon is called.

5: wSetInfoDraw

newParam
LONG: address of info bar draw routine.
result
None.

Called when SetInfoDraw is called.

6: wSetOrigin

newParam
WORD: flag, TRUE to scroll content.
WORD: window's Y origin.
WORD: window's X origin.
result
None.

Called when SetContentOrigin is called.

7: wSetDataSize

newParam
WORD: height of window's data area.
WORD: width of window's data area.
result
None.

Called when SetDataSize is called.

8: wSetZoomRect

newParam
LONG: pointer to new zoom RECT.
result
None.

Called when SetZoomRect is called.

9: wSetTitle

newParam
LONG: pointer to new title.
result
None.

Called when SetWTitle is called.

10: wSetColorTable

newParam
LONG: pointer to new color table.
result
None.

Called when SetFrameColor is called.

11: wSetFrameFlag

newParam
LONG: pointer to new zoom RECT.
result
None.

Called when SetWFrame is called.

12: wGetOrgMask

newParam
None.
result
WORD: window's origin mask.

13: wGetMaxGrow

newParam
None.
result
Low word is window's maximum height when grown.
High word is window's maximum width when grown.

Called when GetMaxGrow is called.

14: wGetScroll

newParam
None.
result
Low word is number of pixels to scroll when arrow is selected.

Called when GetScroll is called.

15: wGetPage

newParam
None.
result
Low word is pixels to scroll when page region is selected.

Called when GetPage is called.

16: wGetInfoRefCon

newParam
None.
result
Value passed to info bar draw routine.

Called when GetInfoRefCon is called.

17: wGetInfoDraw

newParam
None.
result
Address of info bar draw routine.

Called when GetInfoDraw is called.

18: wGetOrigin

newParam
None.
result
Low word is content's Y origin.
High word is content's X origin.

Called when GetContentOrigin is called.

19: wGetDataSize

newParam
None.
result
Low word is window's data height.
High word is window's data width.

Called when GetDataSize is called.

20: wGetZoomRect

newParam
None
result
Pointer to window's current zoom RECT.

Called when GetZoomRect is called.

21: wGetTitle

newParam
None
result
Pointer to window's title.

Called when SetWTitle is called.

22: wGetColorTable

newParam
None.
result
Pointer to window's color table.

Called when SetFrameColor is called.

23: wGetFrameFlag

newParam
None.
result
Low word is window's wFrame field.

Called when SetWFrame is called.

24: wGetInfoRect

newParam
LONG: pointer to place to store info bar's enclosing RECT.
result
None.

Called when GetRectInfo is called.

25: wGetDrawInfo

newParam
None.
result
None.

Called when DrawInfoBar is called.

26: wGetStartInfoDraw

newParam
LONG: pointer to place to store info bar's enclosing RECT.
result
None.

Called when StartInfoDrawing is called.

27: wGetEndInfoDraw

newParam
None.
result
None.

Called when EndInfoDrawing is called.

28: wZoomWindow

newParam
None.
result
None.

Called when ZoomWindow is called.

29: wStartDrawing

newParam
None.
result
None.

Called when StartDrawing is called.

30: wStartMove

newParam
WORD: new y position (global).
WORD: x position (global).
result
Low word is new y position (global).
High word is x position (global).

Called before MoveWindow moves a window.

31: wStartGrow

newParam
None.
result
None.

Called before GrowWindow tracks the growing of a window.

32: wNewSize

newParam
LONG: pointer to:
WORD: proposed new height.
WORD: proposed new width.
These two values can be changed.
result
Low word TRUE if only uncovered content should be drawn.
FALSE if entire content should be redrawn.

Called by SizeWindow before it resizes a window. The new height and width can be changed by modifying the words pointed to by the pointer in newParam.

33: wTask

newParam
LONG: pointer to task record.
WORD: result from FindWindow.
result
Low word is code returned by TaskMaster (zero if handled).
High word is task performed. Returned in TaskData if code is 0.

Called from TaskMaster when it cannot handle a task. If the user presses the mouse button over a window, TaskMaster will call FindWindow to find out what part of the window. TaskMaster will then handle the task if FindWindow returns wInMenuBar or bit 15 of the window pointer is set (system window). Otherwise, the result of FindWindow is passed to wTask to be handled or not.

If the defProc can handle the task it should do so and return zero in the low word of the result (which will be the result to the application returned from TaskMaster) and a code of the task performed in the high word of the result (which is returned to the application in its task record TaskData field). Fields in the task record may also be modified to return parameters to the application as this is the same record passed to TaskMaster.

If the defProc cannot handle the task, it should return the result from FindWindow (the second field in newParam) in the low word of the result. The high word of the result is not used.

For example, the standard document window defProc handles the following results from FindWindow if the taskMask record allows.

wInContent
Brings the window to the top.
wInDrag
Calls DragWindow.
wInGrow
Brings the window to the top. If it is already on the top, it calls GrowWindow and SizeWindow.
wInGoAway
Calls TrackGoAway.
wInZoom
Calls TrackZoom and ZoomWindow.
wInInfo
Brings the window to the top.
wInFrame
Brings the window to the top. If it is already on the top, checks if it is on one of the window's scroll bars, tracks it, and scrolls the window's content as needed.

A custom window defProc can return any code (bit 15 is used for system windows) it wants when it is called to do a hit test. This code would be that returned by FindWindow, and the application would have to know about the code if it called FindWindow instead of TaskMaster. If TaskMaster is used, the code that FindWindow returns is passed back to your defProc with a wCallDefProc and wTask. The defProc could perform any task it wanted: change colors, eject a disk, run a spelling checker, or anything else.

Window Record Definition


     0 |wNext                 | LONG - Pointer to next window record,
       |______________________|_        zero is end of list.
     4 |wPort ///               | BYTE[170] - Window's GrafPort.
       |________________________|
   174 |wDefProc              | LONG - Address of window's definition
       |______________________|        procedure.
   178 |wRefCon               | LONG - Reserved for application's use.
       |______________________|
   182 |wContDraw             | LONG - Address of routine that will draw
       |______________________|        window's content.
   186 |wReserved             | LONG - Reserved by the Window Manager,
       |______________________|        do not use.
   190 |wStrucRgn             | LONG - Handle of window's structure region.
       |______________________|
   194 |wContRgn              | LONG - Handle of window's content region.
       |______________________|
   198 |wUpdateRgn            | LONG - Handle of window's update region.
       |______________________|
   202 |wCtls                 | LONG - Handle of first control in
       |______________________|        window's content.
   206 |wFrameCtls            | LONG - Handle of first control in
       |______________________|        window's frame.
   210 |wFrame     |            WORD - Flags that define window.
       |___________|
   212 |wCustom    ...          BYTE[n] - Additional data space defined by
       |___________...                    window's definition procedure.

The changes use some vacant space under the window port and add the wReserved field to the record for future expansion.

In addition to defining the window record, the wFrame field needs to be further defined. In the diagram below the shaded bits are reserved for use by each window defProc (the values shown are those used by the standard document window defProc). Bits not shaded are reserved by the Window Manager and are applicable to all windows.

Further Reference


This and all of the other Apple II Technical Notes have been converted to HTML by Aaron Heiss as a public service to the Apple II community, with permission by Apple Computer, Inc. Any and all trademarks, registered and otherwise, are properties of their owners.