Drawers

While REALbasic has implemented drawers, some basic functionality is omitted, and the functionality that exists is not stable, in my experience. By foregoing the use of REALbasic functions like ShowWithin, Hide, and the Top, Left, and Width properties in favor of Window Manager functions, the result is a more stable drawer. Writing the REALbasic declarations is entirely straightforward, given what we've covered. So we'll concentrate on wrapping the declares into a class.

To begin such a class, add a new class to a project; set its name to 'DrawerWindow' and its superclass to Window. Next, add a private string constant CarbonLib whose values are "CarbonLib" for Carbon PEF builds, and System/Library/Frameworks/Carbon.Framework for Carbon Mach-O builds.

Drawers first appeared in MacOS 10.2. Thus we should check the OS version to make sure that we can call the related functions. Soft declares make it possible to write code that will compile in earlier MacOS versions.

Private Function IsSupportedOS() as Boolean Static SystemVersion as Integer Static isSupported as Boolean = (System.Gestalt("sysv", systemVersion) and systemVersion >= &h1020) Return isSupported End Function

We engage in a bit of premature optimization to illustrate a use of static variables. Because the compiler only calls the initialization code for a static variable once, we can cache the result of the relatively expensive gestalt call.

The first public method to add to the window is a constructor that sets the drawer parent.

Sub Constructor(theParent as Window) If theParent Is Nil then Return //or raise an exception End if If NOT self.IsSupportedOS then Return End if Soft Declare Function SetDrawerParent Lib CarbonLib (inDrawerWindow as WindowPtr, inParent as WindowPtr) as Integer dim OSError as Integer = SetDrawerParent(self, theParent) If OSError <> 0 then //handle error End if self.Visible = false // call the Window constructor, or Open events will not fire Super.Window() End Sub

Let me briefly discuss error-handling in the code above. We must include a check for the parameter, because passing a nil object to SetDrawerParent will probably result in a crash. If the parameter is nil, then my preference would be to raise an exception. You could instead return, since it is possible to check whether the drawer has a valid parent using the next function. If SetDrawerParent fails, then you could ignore the error. Probably I would log the error for debug builds, and I might log it in the built application. Since SetDrawerParent will be called only if the code is executing in MacOS 10.2 or newer, you do not need to check for availability of the function unless you are a belt-and-suspenders sort.

Next is a function that returns the parent window.

Function Parent() as Window If NOT self.IsSupportedOS then Return Nil End if Soft Declare Function GetDrawerParent Lib CarbonLib (inDrawerWindow as WindowPtr) as Integer dim theParent as Window dim windowRef as Integer = GetDrawerParent(self) For i as Integer = 0 to WindowCount - 1 If Window(i).Handle = windowRef then theParent = Window(i) Exit End if Next Return theParent End Function

I could have just saved a reference to the window, but implementing Parent in this way avoids the possibility of a circular reference. Since a drawer shouldn't exist without a parent, requiring one to be passed in the constructor discourages forgetting to set a Parent property.

Opening and Closing a Drawer

A drawer has one of four states, represented by the four values of the enumerated type WindowDrawerState.

typedef UInt32 WindowDrawerState; enum { kWindowDrawerOpening = 1, kWindowDrawerOpen = 2, kWindowDrawerClosing = 3, kWindowDrawerClosed = 4 };

A WindowDrawerState is a UInt32; that is, an unsigned integer, so it is declared as Integer. The function GetDrawerState returns the state of the drawer.

WindowDrawerState GetDrawerState ( WindowRef inDrawerWindow );

Here we wrap it into a function IsClosed() as Boolean to provide access to the state of the drawer.

Function IsClosed() as Boolean If NOT self.IsSupportedOS then Return false End if Soft Declare Function GetDrawerState Lib CarbonLib (inDrawer as WindowPtr) as Integer Const kWindowDrawerClosed = 4 Return (GetDrawerState(self) = kWindowDrawerClosed) End Function

The function ToggleDrawer changes the drawer's state between open and closed.

OSStatus ToggleDrawer ( WindowRef inDrawerWindow );

Note that ToggleDrawer is asynchronous. According to MacWindows.h, ToggleDrawer installs a timer that toggles the drawer after ToggleDrawer returns. ToggleDrawer sends Carbon events kEventWindowDrawerOpening, kEventWindowDrawerOpened, kEventWindowDrawerClosing, and kEventWindowDrawerClosed that you will know how to handle, if you want, after reading the Carbon events chapter.

We wrap this function into a subroutine Toggle().

Sub Toggle() If NOT self.IsSupportedOS then Return End if Declare Function ToggleDrawer Lib CarbonLib (inDrawerWindow as WindowPtr) as Integer dim OSError as Integer = ToggleDrawer(self) If OSError <> 0 then //handle End if End Sub

Controlling the Offsets

The leading and trailing offset properties of a drawer control the indentation of a drawer relative to its parent. The functions SetDrawerOffsets and GetDrawerOffsets provide access to these properties.

OSStatus SetDrawerOffsets ( WindowRef inDrawerWindow, float inLeadingOffset, float inTrailingOffset );

OSStatus GetDrawerOffsets ( WindowRef inDrawerWindow, float * outLeadingOffset, float * outTrailingOffset );

The DrawerWindow methods that follow wrap these functions. Attempting to control offsets with REALbasic window class functions doesn't work very well.

Sub LeadingOffset(Assigns theValue as Integer) If NOT self.IsSupportedOS then Return End if Soft Declare Function SetDrawerOffsets Lib CarbonLib (inDrawerWindow as WindowPtr, inLeadingOffset as Single, inTrailingOffset as Single) as Integer Const kWindowOffsetUnchanged = -1.0 dim OSError as Integer = SetDrawerOffsets(self, theValue, kWindowOffsetUnchanged) If OSError <> 0 then //handle End if End Sub

Function LeadingOffset() as Integer If NOT self.IsSupportedOS then Return 0 End if Soft Declare Function GetDrawerOffsets Lib CarbonLib (inDrawerWindow as WindowPtr, ByRef outLeadingOffset as Single, ByRef outTrailingOffset as Single) as Integer dim lead, trail as Single dim OSError as Integer = GetDrawerOffsets(self, lead, trail) If OSError <> 0 then //handle End if Return lead End Function

Sub TrailingOffset(Assigns theValue as Integer) If NOT self.IsSupportedOS then Return End if Soft Declare Function SetDrawerOffsets Lib CarbonLib (inDrawerWindow as WindowPtr, inLeadingOffset as Single, inTrailingOffset as Single) as Integer Const kWindowOffsetUnchanged = -1.0 dim OSError as Integer = SetDrawerOffsets(me, kWindowOffsetUnchanged, theValue) If OSError <> 0 then //handle error End if End Sub

Function TrailingOffset() as Integer If NOT self.IsSupportedOS then Return 0 End if Soft Declare Function GetDrawerOffsets Lib CarbonLib (inDrawerWindow as WindowPtr, byRef outLeadingOffset as Single, ByRef outTrailingOffset as Single) as Integer dim lead, trail as Single dim OSError as Integer = GetDrawerOffsets(self, lead, trail) If OSError <> 0 then //handle error End if Return trail End Function

Controlling the Opening Edge

A drawer has a preferred edge, and a current edge. The preferred edge is a settable property that suggests the edge of the parent window from which the drawer emerges. Using the preferred edge may not always be feasible, in which case the OS will switch to the opposite edge. There is a read-only current edge property that tells you from which edge the drawer actually appears. Here are the REALbasic methods wrapping the OS functions.

Sub PreferredEdge(Assigns theValue as Integer) If NOT self.IsSupportedOS then Return End if Soft Declare Function SetDrawerPreferredEdge Lib CarbonLib (inDrawerWindow as WindowPtr, inEdge as Integer) as Integer dim OSError as Integer = SetDrawerPreferredEdge(self, theValue) If OSError <> 0 then //handle error End if End Sub

Function PreferredEdge() as Integer If NOT self.IsSupportedOS then Return 0 End if Soft Declare Function GetDrawerPreferredEdge Lib CarbonLib (inDrawerWindow as WindowPtr) as Integer Return GetDrawerPreferredEdge(self) End Function

Function CurrentEdge() as Integer If NOT self.IsSupportedOS then Return 0 End if Soft Declare Function GetDrawerCurrentEdge Lib CarbonLib (inDrawerWindow as WindowPtr) as Integer Return GetDrawerCurrentEdge(self) End Function