

|
Volume Number: | 7 | |
Issue Number: | 10 | |
Column Tag: | Color Workshop |
Related Info: Menu Manager Memory Manager Color Quickdraw
Window Menu Bars Revisited
By John A. Love, III, Springfield, VA, MacTutor Contributing Editor
Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.
[ John is a member of the Washington Apple Pi Users’ Group from the greater Washington D.C. metropolitan area and can be reached on AppleLink {D3471} and on GEnie {J.LOVE7} ... with very, very special thanks to Ben Cranston from the Programmers’ Special Interest Group of Washington Apple Pi for his brilliance and his extraordinary patience ... ]
This article builds on James Matthews’ work presented in MacTutor, Nov 1988. My significant additions include:
Color menus.
Hierarchical menus.
Multiple windows with the capability to place varying Window MenuBars (WMB) in different windows.
Zooming and growable windows that allow the user to scroll the Window MenuBars horizontally by clicking on my horizontal Menu Scroll Activators (MSA).
By the way, you’ll notice that the above is a THINK Pascal Project, using version 3.Ø.2 and the appropriate Interface files and Libraries provided on the System 7 CD-ROM. Yes...my application is not only System 7 “compatible”, but System 7 “friendly”...but more on this later.
There are two fundamental sets of PROCEDUREs and FUNCTIONs that are mandatory:
• Some of Skippy White’s Off-Screen Map routines that were originally presented by MacDTS on CD-ROM. The ones I use are modified to address off-screen BitMaps/PixMaps rather than off-screen Devices. I presented my modifications in MacTutor, Nov 1990, “Spiffy Color Demo, Part I”. I am re-running them below because:
a) They really ought to serve as a programming standard and, therefore, are worth repeating.
b) I now use the FUNCTION CreateOffScreen to return a OffScreenRecHdl since I draw the window’s WMB in the associated off-screen Port & subsequently blit the Port’s bitMap/pixMap back to the screen using CopyBits. Since each window can have a different WMB, each window can have a different OffScreenRecHdl for blitting back upon the occurrence of Update and Activate Events. I originally implemented this logic so I could have these OffScreenRec handles hang around even when said window had been de-activated or dragged partly beyond the physical edge of the screen or dragged to a secondary monitor. However, I then decided to dispose of these handles immediately following the blitting back to screen ... otherwise, I would then have to periodically test, for example, if any of this dragging had occured and, if so, re-create all these off-screen Maps anyway.
• James Matthews’ routines such as wInsertMenu and wDrawMenuBar. I will attempt to lead you readers through the logic with particular emphasis on color and hierarchical Menus.
One note of caution ... I will not be presenting alot of the conventional parts of the code, for example the GetNextEvent/WaitNextEvent loop, the cursor-changing PROC etc. The entire THINK Pascal Project, Pascal source code and SARez source( w/included resources) consumes a tad over 600K which Kirk Chase will gladly sell you for a mere pittance.
Before the drum roll ends, I will also be talking about snipits of code that are just plain spiffy. For example, how do you retrieve a window’s window type ... before you generate a wrong answer such as a simple call to GetWVariant, watch out ... the variation codes of several rDocProc types duplicate those of several standard types. So, stay tuned ...
First in line is a complete listing of all external routines USEd by each Pascal UNIT ... the big picture, so-to-speak. I will amplify only on a few of them, those that are crucial to my off-screen BitMap/PixMap gymnastics as well as to Window MenuBars.
{1} UNIT wBMMiscSubs; INTERFACE USES Types, Quickdraw, Menus, TextEdit, Traps, Sound, wBMGlobals; PROCEDURE InitManagers; FUNCTION TestForColor (VAR pixelDepth: INTEGER): BOOLEAN; PROCEDURE LocalGlobal (VAR r: Rect); PROCEDURE GlobalLocal (VAR r: Rect); FUNCTION TrapIsAvailable (theTrap: INTEGER): BOOLEAN; FUNCTION WNEisImplemented: BOOLEAN; PROCEDURE PlaySound (mySound: Str255); FUNCTION GetStripAddressMask: LONGINT; FUNCTION QuickStrip (myPtr: Ptr): Ptr; FUNCTION GetMouseMovement (gMouse0: Point): Size; FUNCTION DoubleClick: BOOLEAN; PROCEDURE DimRgn (rgn: RgnHandle); FUNCTION Max (a, b: INTEGER): INTEGER; FUNCTION Min (a, b: INTEGER): INTEGER; FUNCTION GetWindowPartColor (window: WindowPtr; part: INTEGER; VAR color: RGBColor): BOOLEAN; UNIT OffscreenSubs; INTERFACE USES Quickdraw, wBMInterface, wBMGlobals, wBMMiscSubs; FUNCTION GetMaxAreaDevice (globalRect: Rect): GDHandle; FUNCTION CreateOffScreen (VAR myRect: Rect): OffScreenRecHdl; PROCEDURE ToOnScreen (COSHdl: OffScreenRecHdl); PROCEDURE DisposOffScreen (VAR COSHdl: OffScreenRecHdl); UNIT wBMScrollSubs; INTERFACE USES Quickdraw, wBMGlobals, wBMMiscSubs; FUNCTION ScrollHoriz (wp: WindowPtr): ControlHandle; FUNCTION ScrollVert (wp: WindowPtr): ControlHandle; PROCEDURE ScrollShow (wp: WindowPtr); PROCEDURE ScrollHide (wp: WindowPtr); PROCEDURE InvalidScroll (wp: WindowPtr); PROCEDURE ValidScroll (wp: WindowPtr); PROCEDURE ScrollResize (wp: WindowPtr); FUNCTION GetWindowType (window: WindowPtr): INTEGER; UNIT wBarMenuProc; INTERFACE USES Quickdraw, wBMInterface, wBMGlobals, wBMMiscSubs, OffScreenSubs, wBMScrollSubs; FUNCTION wInitMenus: wMenuBarListHdl; FUNCTION wGetNewMBar (wp: WindowPtr; wMenuBarID: INTEGER): wMenuBarHandle; FUNCTION wGetMenuBar (wp: WindowPtr): wMenuBarHandle; PROCEDURE wSetMenuBar (theMenuBar: wMenuBarHandle; wp: WindowPtr); PROCEDURE wAddWMB (theMenuBar: wMenuBarHandle); PROCEDURE wDeleteWMB (wp: WindowPtr); PROCEDURE wClearMenuBarList (theMenuBarList: wMenuBarListHdl); PROCEDURE wInsertMenu (theMenuBar: wMenuBarHandle; theMenu: MenuHandle; beforeID: INTEGER); PROCEDURE wDeleteMenu (theMenuBar: wMenuBarHandle; menuID: INTEGER); PROCEDURE GetWBMrects (wp: WindowPtr; VAR WBMrect, leftMSArect, rightMSArect: Rect); { only local: } { PROCEDURE wSetColorMenu (menusID, itemsID: INTEGER); } PROCEDURE wDrawMenuBar (theMenuBar: wMenuBarHandle); PROCEDURE wScrollMenuBar (theMenuBar: wMenuBarHandle); PROCEDURE wGetMSA (theMenuBar: wMenuBarHandle); PROCEDURE wDrawMSA (theMenuBar: wMenuBarHandle); PROCEDURE wClearMenuBar (theMenuBar: wMenuBarHandle); FUNCTION wMenuSelect (theMenuBar: wMenuBarHandle; startPt: Point): LONGINT; FUNCTION wMenuKey (theMenuBar: wMenuBarHandle; ch: char): LONGINT; PROCEDURE wHiliteMenu (theMenuBar: wMenuBarHandle; menuID: INTEGER); PROCEDURE wChangeMenuBarSize (wp: WindowPtr; zooming: BOOLEAN); UNIT wBMWindSubs; INTERFACE USES Quickdraw, Palettes, wBMInterface, wBMGlobals, wBMMiscSubs, wBMScrollSubs, wBarMenuProc, wBMBalloons; FUNCTION InitWindowStorage: allWSHdl; PROCEDURE CalcWindowRect (window: WindowPtr; VAR r: Rect); PROCEDURE DisplayWindow (window: WindowPtr; TLPR: Point); FUNCTION DoNewWindow (windowID, wMBARID: INTEGER; VAR offset: Point): BOOLEAN; PROCEDURE CloseOurWindow (window: WindowPtr); PROCEDURE DoCloseAll;
Next is my complete wBMGlobals.p file. I wouldn’t even bother with it except for the RECORD types that serve as the chief worker-bees for off-screen BitMaps/PixMaps, WMBs and multiple windows.
{2} UNIT wBMGlobals; INTERFACE { ------------------------------------------ } { Global constants: } { ------------------------------------------ } CONST { General constant: } Enter = 3; ohFudge = 1; { Guess what ????? } { low-memory global: } ROM85Loc = $28E; { Specific constants ... } { Stuff for main Menu ... } kMBarDisplayed = 128; mainMBARID = kMBarDisplayed; AppleMenuID = 1; AboutItem = 1; AdisabledItem = 2; { ---- } FileMenuID = 2; NewWindowItem = 1; CloseWindowItem = 2; FdisabledItem = 3; QuitItem = 4; { ---- } EditMenuID = 3; UndoItem = 1; EdisabledItem = 2; CutItem = 3; CopyItem = 4; PasteItem = 5; ClearItem = 6; { Stuff for Window Menu ... } kMBarNotDisplayed = 129; wMBARID = kMBarNotDisplayed; wAppleMenuID = 1001; wAboutItem = 1; wAdisabledItem = 2; { ---- } wFileMenuID = 1002; wNewWindowItem = 1; wCloseWindowItem = 2; wFdisabledItem = 3; wQuitItem = 4; { ---- } wEditMenuID = 1003; wUndoItem = 1; wEdisabledItem = 2; wCutItem = 3; wCopyItem = 4; wPasteItem = 5; wClearItem = 6; { -------------------------------------------- } { Hierarchical Menus ... } { -------------------------------------------- } wNewHierMenuID = 104; wNewHierItem = 1; wCloseHierMenuID = 105; wCloseHierItem = 1; wHierHierMenuID = 106; wHierHierItem = 1; { Window goodies ... } maxWindows = 20; { A # subject only to memory. } kDefaultWindowID = 1001; mainWindowID = kDefaultWindowID; newWindowID = mainWindowID + 1; frame = 1; { Window parts in Pixels ... } shadow = 1; title = 18; horizScrollID = 128; vertScrollID = 129; scrollWidth = 16; scrollHeight = scrollWidth; growBoxSize = scrollWidth - frame; { Miscellaneous stuff ... } logoID = 131; { -------------------------------------------------------------------------------------------------------------- } { ... for Error handling in my Off-screen map routine(s): } { -------------------------------------------------------------------------------------------------------------- } NewCOSHdlError = -10000; MaxDevError = -15000; NewBaseAddrPtrError = -20000; CloneHdlError = -25000; { ------------------------------------ } { MultiFinder stuff: } { ------------------------------------ } { _WaitNextEvent = $A860; -- in new “Traps.p” interface } { _Unimplemented = $A89F; } SysEnvironsVersion = 1; { OSEvent is the event number of the suspend/resume and } { mouse-moved Events sent by MultiFinder. Once you } { determine that an event is an OSEvent, look at the } { High byte of the message sent with the event to } { determine which kind it is. To differentiate between } { suspend & resume, look at the resumeMask bit. } OSEvent = app4Evt; suspendResumeMessage = 1; mouseMovedMessage = $FA; resumeMask = 1; { ------------------------------------------------------------------ } { ... for Window Menu Bar routines: } { ------------------------------------------------------------------ } noneHilited = -1; { Stored in “wMenuBar.hilited” } { if nada. } menuTitleBit = 31; { Offset for Menu title bit in } { “enableFlags”. } flashDelay = 10; { For blinking Menu title } { and Menu item. } normalSize = 12; { Radius FPD stuff ... } chicago16 = 16; { Special FONT. } dontScrollMenu = 0; { Pass to wDrawMenuBar } { if appropriate. } atEnd = 0; { ... as in InsertMenu(MenuHdl, 0); } noMBARrsrc = -1; { There ‘aint’ any ... } mceMenuBar = 0; { mctID & mctItem for } { color Menus ... } mceMenuTitle = 0; zooming = TRUE; regMenu = 0; { NOT a Hierarchical Menu. } mainMenu = 0; { NO parent Menu. } { ------------------------------------------ } { Global types: } { ------------------------------------------ } TYPE { General stuff: } RgnHandlePtr = ^RgnHandle; wordPtr = ^INTEGER; longPtr = ^LONGINT; { BitMapPtr = ^BitMap; -- in new “QuickDraw.p” interface } { The following is required to avoid calling } { _GetNewWindow with NIL as the “wStroage” } { parameter. Otherwise, we risk fragmenting } { the Application Heap. } { } { [Adapted from Dan Weston’s Assembly Code] } oneWStorage = RECORD inUse: BOOLEAN; fill: BOOLEAN; ws: WindowRecord; END; allWStorage = RECORD ones: ARRAY[1..maxWindows] OF oneWStorage; END; allWSPtr = ^allWStorage; allWSHdl = ^allWSPtr; { Off Screen stuff: } OffScreenRec = RECORD CreateOffScreenError: OSErr; oldDevice: GDHandle; origPort: GrafPtr; drawingRect: Rect; myMaxDevice: GDHandle; myBits: Handle; offGrafPort: GrafPort; offGrafPtr: GrafPtr; offCGrafPort: CGrafPort; offCGrafPtr: CGrafPtr; ourCTHandle: CTabHandle; offBitMapPtr, onScreenBitsPtr: BitMapPtr; END; OffScreenRecPtr = ^OffScreenRec; OffScreenRecHdl = ^OffScreenRecPtr; { ... for Window Menus: } wMenuRec = RECORD mh: MenuHandle; menuType: INTEGER; { Regular or Hierarchical. } parentID: INTEGER; { Hierarchical stuff ... } menuParentRgn, cumParentRgn: RgnHandle; titleRect: Rect; MenuDownOSHdl: OffScreenRecHdl; END; wMenuBar = RECORD numMenus: INTEGER; barLength: INTEGER; titleHilited: INTEGER; { Menu # or “noneHilited”. } leftScrollPoly, rightScrollPoly: PolyHandle; { ... to Menu Scroll Activators (MSA). } saveCumScrollMenuX: INTEGER; barOSHdl: OffScreenRecHdl; wp: WindowPtr; wMCTable: MCTableHandle; wMenus: ARRAY[0..0] OF wMenuRec; END; wMenuBarPtr = ^wMenuBar; wMenuBarHandle = ^wMenuBarPtr; { ---------- } wMenuBarList = RECORD numWindows: INTEGER; wMBars: ARRAY[0..0] OF wMenuBarHandle; END; wMenuBarListPtr = ^wMenuBarList; wMenuBarListHdl = ^wMenuBarListPtr; aDynamicBalloon = RECORD { ... for my NEW dynamic windows. } dynamicStrID: INTEGER; dynamicStrIndex: INTEGER; dynamicR: Rect; END; { The BIG guys: } RadBWStatus = PACKED RECORD { For Internal use, ONLY !! } Signature, CPFlags, SSDelay, VertOffset: Char; LargeFontEn, PluggedIn: Char; { PluggedIn = DontRepos } MacBits: BitMapPtr; BigTicksPtr: Ptr; BigTicks: LONGINT; Reserved1: LONGINT; TopBigRAM, IdleHook: Ptr; Reserved2: LONGINT; CursorHook: Ptr; END; RadBWStatPtr = ^RadBWStatus; RadBWStatHdl = ^RadBWStatPtr; { ---------- } RadIIStatus = PACKED RECORD AutoCenter, AutoLower, TearOffMenus, ScreenDump: Char; LargeMenus, ScreenSaver, SaverActive, Reserved: Char; ScreenSaverDelay, InitVers: INTEGER; CardID: PACKED ARRAY[0..5] OF Char; ROMVers: PACKED ARRAY[0..5] OF INTEGER; END; RadIIStatPtr = ^RadIIStatus; RadIIStatHdl = ^RadIIStatPtr; { ---------- } PivotDataStruct = PACKED RECORD flipped, command, dLogFlags, xInternal, topOffset, bottomOffset: Char; tileFactor: INTEGER; reserved: PACKED ARRAY[0..4] OF Char; movementFlags: Char; parameter, result: LONGINT; resultRect: Rect; END; PivotDSPtr = ^PivotDataStruct; PivotDSHand = ^PivotDSPtr; { ---------- } radiusType = (none, radBW, radII); RadiusData = RECORD PivotHdl: PivotDSHand; CASE radType : radiusType OF none: ( ); radBW: ( BWHdl: RadBWStatHdl; ); radII: ( IIHdl: RadIIStatHdl; ); END; RadiusDataPtr = ^RadiusData; RadiusDataHdl = ^RadiusDataPtr; { -------------------------------------- } { Global variables: } { -------------------------------------- } VAR { ... for Main PROGRAM: } gStripAddressMask: LONGINT; oldPort: GrafPtr; lScreen, gScreen, visRect, updateRect: Rect; ROM: wordPtr; mBarHt: INTEGER; AppleMenu, FileMenu, EditMenu: MenuHandle; aMac2, hasGrowIcon: BOOLEAN; colorDepth: INTEGER; TheWindow, FW: WindowPtr; windowCount, newCount, windType: INTEGER; windowStorage: allWSHdl; Event: EventRecord; windowLoc: INTEGER; ignore, moreNew, brandNew: BOOLEAN; offset, deltaOffset: Point; { ... to avoid flickering Menu Bar: } prevDA, prevWind, currDA, myAppl, applWind: BOOLEAN; Done, InWindow, InWBMenu: BOOLEAN; WNE, InForeGround: BOOLEAN; Sleep, finalTicks: LONGINT; ourControl: ControlHandle; { Window Menus ... } pnState: PenState; WBMrect, leftMSArect, rightMSArect, gWBMrect: Rect; mbHState: SignedByte; oldClip, onScreenRgn: RgnHandle; oldForeColor, oldBackColor: RGBColor; { Color stuff ... } oldMCTable, newMCTable: MCTableHandle; found: BOOLEAN; mBarList: wMenuBarListHdl; mBar: wMenuBarHandle; newBarListSize, newWMBSize: Size; titleWidth, scrollPolyDXY: INTEGER; { Pixels between adjacent Menus & } { pixels to invert on each side } { of Menu title: } betweenTitles, invertOverlap: INTEGER; aboveBelowItem, menuFrame, menuShadow, frameShad: INTEGER; whichMenu, whichItem: INTEGER; { Radius’ BIG screens ... } WMgrPort: GrafPtr; FPDRsrc: RadiusData; BIGfont: Handle; sizeFont: INTEGER; { System 7.0 ... } startBalloons, balloonsUp, saveBalloons: BOOLEAN; HelpMenu: MenuHandle; origNumHelpItems: INTEGER; dynamicBalloons: ARRAY[0..2] OF aDynamicBalloon; lastBalloon: INTEGER; IMPLEMENTATION END. { UNIT = wBMGlobals }
Speaking of multiple windows and one of those snipits I promised you, the principal problem here centers on the fact that the majority of code I’ve seen repeatedly calls GetNewWindow, passing NIL as the wStorage Pointer. Bad news!!! The window record will be allocated as a non-relocatable object on the heap in which case you risk generating a fragmented heap. Dan Weston, in his positively super two-book set, The Complete Book of Macintosh Assembly Language Programming, passes a Pointer to the ws field of a oneWStorage RECORD and sets its inUse field. You see ... we’ve initialized a NewHandle to a allWStorage RECORD shortly after our call to InitManagers. This new handle contains, say, 20 oneWStorage RECORDs:
{3} FUNCTION InitWindowStorage: allWSHdl; { Call this hummer after “InitManagers”. } VAR wsHdl: allWSHdl; BEGIN InitWindowStorage := NIL; { Worry-wart !! } wsHdl := allWSHdl(NewClearHandle(SizeOf(allWStorage))); IF MemError = noErr THEN BEGIN MoveHHi(Handle(wsHdl)); HLock(Handle(wsHdl)); ; windowCount := 0; InitWindowStorage := wsHdl; END; { IF noErr } END; { InitWindowStorage } Every time you call GetNewWindow, you scan these 20 and stop at the first whose inUse = FALSE & set it to TRUE. FUNCTION DoNewWindow (windowID, wMBARID: INTEGER; VAR offset: Point): BOOLEAN; { IF NOT DoNewWindow( ) THEN } { OhOh; } VAR i: INTEGER; FUNCTION FindWStorage (VAR index: INTEGER): BOOLEAN; BEGIN index := 1; WITH windowStorage^^ DO BEGIN WHILE (index <= maxWindows) & ones[index].inUse DO index := index + 1; ; IF index <= maxWindows THEN BEGIN ones[index].inUse := TRUE; FindWStorage := TRUE; END ELSE { no more windows allowed } FindWStorage := FALSE; END; { WITH } END; { FindWStorage } BEGIN{ DoNewWindow } DoNewWindow := FALSE; ; moreNew := moreNew & FindWStorage(i); { i is VARed. } IF NOT moreNew THEN EXIT(DoNewWindow); IF aMac2 THEN TheWindow := GetNewCWindow(windowID, @windowStorage^^.ones[i].ws, WindowPtr(-1)) ELSE TheWindow := GetNewWindow(windowID, @windowStorage^^.ones[i].ws, WindowPtr(-1)); ; IF TheWindow = NIL THEN BEGIN { Reverse effect of FindWStorage: } windowStorage^^.ones[i].inUse := FALSE; EXIT(DoNewWindow); END; ; SetPort(TheWindow); IF windowID = newWindowID THEN BEGIN ourControl := GetNewControl(horizScrollID, TheWindow); ourControl := GetNewControl(vertScrollID, TheWindow); END; { retrieve Scroll Bars } { oldOffset --> newOffset: } offset := GetTLWindPortRect(TheWindow, offset); mBar := wGetNewMBar(TheWindow, wMBARID); IF mBar <> NIL THEN wAddWMB(mBar); ScrollResize(TheWindow); { Does nada if NO Scroll Bars. } DisplayWindow(TheWindow, offset); { Since an Update Event draws the MSAs & the Window Menu, } { Bar we do NOT want these drawn by the DoActivate PROC: } brandNew := TRUE; DoNewWindow := TRUE; ; windowCount := windowCount + 1; moreNew := moreNew & (windowCount < maxWindows); END; { DoNewWindow }
Every time you call CloseWindow, you scan the same 20 and stop when inUse = TRUE and WindowPtr = @oneWStorage.ws . At this point, of course, we reset the former to FALSE so that my DoNewWindow FUNCTION finds it available.
{4} { ---------------------------------------------- } { One at a time, folks !! } { ---------------------------------------------- } PROCEDURE CloseOurWindow (window: WindowPtr); VAR myPic: PicHandle; pal: PaletteHandle; aux: BOOLEAN; auxWind: AuxWinHndl;
The next snipit centers on my comparison of FrontWindow’s resultant WindowPtr with @oneWStorage.ws as I’ve already talked about..
PROBLEM-- oneWStorage is a field in a LOCKED allWStorage handle and, therefore, @oneWStorage.ws has its high bit set. Guess what ... FrontWindow’s WindowPtr has its high bit clear. I “borrowed” GetStripAddressMask and QuickStrip from Tech Note #213.
{5} PROCEDURE DisposeWStorage (wp: WindowPtr); VAR i: INTEGER; found: BOOLEAN; wsHState: SignedByte; BEGIN found := FALSE; { oneWStorage dsec 0 } { inUse byte ; = 0 } { fill byte ; = 1 } { ws byte WindowSize ; = 2 } { dend } { ... } { move.l windowStorage,a0 } { move.l (a0),a4 ; Locked Master Ptr. } { lea ws(a4),a1 ; Bit #31 also set. } { cmpa.l wp,a1 ; wp = 8(a6) } { ... } wsHState := HGetState(Handle(windowStorage)); windowStorage^ := allWSPtr(QuickStrip(Ptr(windowStorage^))); WITH windowStorage^^ DO BEGIN FOR i := 1 TO maxWindows DO IF ones[i].inUse THEN IF wp = @ones[i].ws THEN BEGIN found := TRUE; Leave; END; IF found THEN ones[i].inUse := FALSE {Undo effect of FindWStorage.} ELSE { should NOT happen !! } ; END; { WITH } ; { We have NOT done anything to move memory, } { therefore _MoveHHi is NOT required. } HSetState(Handle(windowStorage), wsHState); END; { DisposeWStorage } BEGIN { CloseOurWindow } IF window = NIL THEN EXIT(CloseOurWindow); IF WindowPeek(window)^.windowKind < 0 THEN CloseDeskAcc(WindowPeek(window)^.windowKind) ELSE BEGIN IF aMac2 THEN BEGIN pal := GetPalette(window); IF pal <> NIL THEN DisposePalette(pal); END; { IF aMac2 } myPic := GetWindowPic(window); IF myPic <> NIL THEN BEGIN HUnlock(Handle(myPic)); ReleaseResource(Handle(myPic)); END; { IF myPic <> NIL } wDeleteWMB(window); DisposeWStorage(window); CloseWindow(window); WITH offset DO BEGIN h := h - deltaOffset.h; IF h < 0 THEN h := 0; v := v - deltaOffset.v; IF v < 0 THEN v := 0; END; { WITH } windowCount := windowCount - 1; ; IF windowCount = 0 THEN BEGIN { In case a lingering DA doesn’t properly } { handle its “doCursor” routine: } InitCursor; { Wait till last is gone because all windows } { share a common Color Table. } IF aMac2 THEN BEGIN aux := GetAuxWin(window, auxWind); IF aux THEN ReleaseResource(Handle(auxWind)); END; { IF aMac2 } END; { IF no more windows } moreNew := TRUE; END; { NOT a Desk Accessory window } END; { CloseOurWindow }
Then comes my OffScreenRec which doesn’t make sense until we see the code later on ... so cool it ...
Next comes the stuff for WMBs. The wMenuBarList consists of a window count and an array of wMenuBarHandles. Gotta have the count so we know how many handles. Each of these handles consists of:
a) menu count and associated array of individual wMenuRecs. Again, this count tells us how many.
b) barLength tells us if we’re clicking the Mouse on ANY active Menu. If so, then we call PtInRect for the titleRect of each wMenuRec to find out which Menu.
c) titleHilited enters with wHiliteMenu which is called as our MouseDown travels from Menu-to-Menu: un-Hilite the old titleRect and Hilite the new.
d) left & right PolyHandles, my MSAs for horizontal scrolling of the WMB. Click on one of these dudes to scroll. If we scroll part of a titleRect out of view, call FillPoly(polyH, black); when we scroll back into view, call FillPoly(polyH, white) followed by FramePoly(polyH).
e) gotta save the amount we’ve scrolled the WMB so when we zoom back in from just zooming out, we can re-scroll to where we were.
f) it’s the OffScreenRecHdl we scroll for instantaneous blitting on screen.
g) the MCTableHandle handles color ... natch !!
h) each wMenuRec basically is just a Menuhandle together with some added info:
1) the menuType determines the placement of the titleRect ... for a regular Menu, it’s in the main WMB and for a hierarchical Menu, it’s the rect of the parent item.
2) the parentID for a hierarchical Menu is recorded to help us determine which wMenus[i] we’re looking at.
3) the two RgnHandles play in the wMenuSelect FUNCTION as our MouseDown bounces between regular, hierarchical and even hierarchical-hierarchical Menus. We create another OffScreenREcHdl for the drawing of the pulled-down Menu. Because of hierarchical Menus, several of these off-screen beauties may exist simultaneously. So we’ve gotta keep track of these off-screen handles so we can correctly dispose of them when we veer off-course ... either to a different Menu entirely or to a different item, an item that does NOT have a sub-Menu. The above RgnHandles help us to do just that.
4) The drawing of the pulled-down Menus occurs within MenuDownOSHdl.
Finally, holding up the rear, the BIG guns from Radius ...
Another snipit enters in the UNIT wBMMiscSubs with InitManagers. Instead of calling MoreMasters 10 times or whatever number turns you on, check out Tech Note #53.
Now, my off-screen gymnastics without many comments since the routines are essentially repeated from one of my former articles:
{6} UNIT OffscreenSubs; INTERFACE USES wBMInterface, wBMGlobals, wBMMiscSubs; FUNCTION GetMaxAreaDevice (globalRect: Rect): GDHandle; FUNCTION CreateOffScreen (VAR myRect: Rect): OffScreenRecHdl; PROCEDURE ToOnScreen (COSHdl: OffScreenRecHdl); PROCEDURE DisposOffScreen (VAR COSHdl: OffScreenRecHdl); { ********** } FUNCTION GetMaxAreaDevice (globalRect: Rect): GDHandle; { Find the greatest overlap device } { for the given global rectangle. } VAR area: LONGINT; maxArea: LONGINT; device: GDHandle; intersection: Rect; BEGIN GetMaxAreaDevice := NIL; ; maxArea := 0; ; device := GetDeviceList; WHILE device <> NIL DO BEGIN IF TestDeviceAttribute(device, screenDevice) THEN IF TestDeviceAttribute(device, screenActive) THEN IF SectRect(globalRect, device^^.gdRect, intersection) THEN BEGIN WITH intersection DO area := LONGINT(right - left) * LONGINT(bottom - top); IF area > maxArea THEN BEGIN GetMaxAreaDevice := device; maxArea := area; END; { IF area > maxArea } END; { IF SectRect ... } device := GetNextDevice(device); END; { WHILE device <> NIL } END; { GetMaxAreaDevice } FUNCTION CreateOffScreen (VAR myRect: Rect): OffScreenRecHdl; { Reference: Tech Note #120 } { with special thanks to: } { Forrest Tanaka and } { Jon Zap of MacDTS } { } { NOTE: Local window coordinates are input, but local } { device coordinates are returned for drawing purposes. } VAR offRowBytes, sizeOfOff, tempSeed: LONGINT; localRect, globRect: Rect; i, maxDepth: INTEGER; err: OSErr; COSHdl: OffScreenRecHdl; PROCEDURE ErrorOut (error: OSErr); BEGIN IF error = NewCOSHdlError THEN CreateOffScreen := NIL ELSE BEGIN COSHdl^^.CreateOffScreenError := error; CreateOffScreen := COSHdl; END; EXIT(CreateOffScreen); END; { ErrorOut } BEGIN { CreateOffScreen } COSHdl := OffScreenRecHdl(NewClearHandle(SizeOf(OffScreenRec))); ; IF MemError <> noErr THEN ErrorOut(NewCOSHdlError); ; MoveHHi(Handle(COSHdl)); HLock(Handle(COSHdl)); { Lock this sucker down !! } WITH COSHdl^^ DO BEGIN { CreateOffScreenError := noErr; -- We hope !! } GetPort(origPort); { Used by ToOnScreen. } drawingRect := myRect; { Saved for use after } { call to ToOnScreen. } globRect := myRect; { We’re about to switch } { the Port to off-screen: } LocalGlobal(globRect); IF NOT aMac2 THEN BEGIN offGrafPtr := @offGrafPort; OpenPort(offGrafPtr); maxDepth := 1; END { ... a low-life machine } ELSE BEGIN myMaxDevice := GetMaxDevice (globRect); IF myMaxDevice = NIL THEN ErrorOut(MaxDevError); oldDevice := GetGDevice; SetGDevice(myMaxDevice); ; offCGrafPtr := @offCGrafPort; { Initialize this guy. } OpenCPort(offCGrafPtr); MoveHHi(Handle(offCGrafPtr^.portPixMap)); { Arrgh !! } HLock(Handle(offCGrafPtr^.portPixMap)); maxDepth := offCGrafPtr^.portPixMap^^.pixelSize; END; { ELSE: aMac2 } { CanNOT use my GlobalLocal PROC because we may have dragged } { our window to a secondary screen. “Global” here is with } { respect to the main screen and “Local” is with respect to } { the secondary screen. } { } { From Forrest Tanaka: } { When GetMaxDevice returns the secondary screen’s GDevice & } { we set that to the current GDevice, then OpenCPort creates } { a CGrafPort which has a portRect=GetMainDevice^^.gdPMap^^. } { bounds which is in the global coordinates for all the } { screens’ pixel images with a topLeft = (0,0). The new } { CGrafPort.portPixMap^^.bounds is in the local coordinates } { of the secondary screen with a topLeft = (0,-640), e.g. } { } { In effect, the port that OpenCPort gives you is NOT a } { port because the portRect pertains to the wrong screen. } { This means that calling GlobalLocal shifts the localRect } { waaaaay over somewhere ... BECAUSE the difference between } { the above portRect and portPixMap^^.bounds is SO large!!! } localRect := globRect; WITH screenBits.bounds DO OffsetRect(localRect, left, top); ; IF aMac2 THEN BEGIN RectRgn(offCGrafPort.visRgn, localRect) offCGrafPort.portRect := localRect; END ELSE BEGIN RectRgn(offGrafPort.visRgn, localRect); offGrafPort.portRect := localRect; END; WITH localRect DO BEGIN offRowBytes := (maxDepth * (right - left) + 15) DIV 16; IF ODD(offRowBytes) THEN offRowBytes := offRowBytes + 1; offRowBytes := offRowBytes * 2; sizeOfOff := LONGINT(bottom - top) * offRowBytes; END; { WITH } myBits := NewClearHandle(sizeOfOff); IF MemError <> noErr THEN ErrorOut(NewBaseAddrPtrError); MoveHHi(myBits); HLock(myBits); IF aMac2 THEN BEGIN WITH offCGrafPtr^.portPixMap^^ DO BEGIN baseAddr := myBits^; rowBytes := offRowBytes + $8000; bounds := localRect; END; { WITH } offBitMapPtr := BitMapPtr(offCGrafPtr^.portPixMap^); END { IF aMac2 } ELSE { definitely ... “YUCKY” black-and-white. } BEGIN WITH offGrafPtr^.portBits DO BEGIN baseAddr := myBits^; rowBytes := offRowBytes; bounds := localRect; END; offBitMapPtr := @offGrafPtr^.portBits; END; IF aMac2 THEN BEGIN ourCTHandle := myMaxDevice^^.gdPMap^^.pmTable; err := HandToHand(Handle(ourCTHandle)); { Clone it. } IF err <> noErr THEN ErrorOut(CloneHdlError); WITH ourCTHandle^^ DO FOR i := 0 TO ctSize DO ctTable[i].value := i; ourCTHandle^^.ctFlags := BAND(ourCTHandle^^.ctFlags, $7FFF); tempSeed := GetCTSeed; { Thanks, Scott Knaster !! } ourCTHandle^^.ctSeed := tempSeed; offCGrafPtr^.portPixMap^^.pmTable := ourCTHandle; END; { IF aMac2 } myRect := localRect; { Return local device coords. } END; { WITH COSHdl^^ DO } ErrorOut(noErr); { Whew !! } END; { CreateOffScreen} { ******************* } { Back to “Square 1”: } { ******************* } PROCEDURE ToOnScreen (COSHdl: OffScreenRecHdl); BEGIN WITH COSHdl^^ DO { COSHdl is locked coming in. } BEGIN SetPort(origPort); IF aMac2 THEN SetGDevice(oldDevice); END; { WITH } END; { ToOnScreen} PROCEDURE DisposOffScreen (VAR COSHdl: OffScreenRecHdl); LABEL 100; BEGIN IF COSHdl = NIL THEN EXIT(DisposOffScreen); WITH COSHdl^^ DO BEGIN IF CreateOffScreenError = MaxDevError THEN GOTO 100; { NewBaseAddrPtrError or CloneHdlError or noErr ... } IF aMac2 THEN BEGIN IF CreateOffScreenError = noErr THEN DisposHandle(Handle(ourCTHandle)); HUnlock(Handle(offCGrafPtr^.portPixMap)); CloseCPort(offCGrafPtr); END ELSE ClosePort(offGrafPtr); IF CreateOffScreenError <> NewBaseAddrPtrError THEN BEGIN HUnlock(myBits); DisposHandle(myBits); END; END; { WITH } 100: HUnlock(Handle(COSHdl)); DisposHandle(Handle(COSHdl)); COSHdl := NIL; { Mark as gone ... } END; { DisposOffScreen } END. { UNIT = OffscreenSubs }
Another snipit centers on the fact that my HandleCursor PROC demands knowledge of what kind of window FrontWindow is:
{7} FUNCTION GetWindowType (window: WindowPtr): INTEGER; { Here, window type = Window Definition ID. } CONST RomMapInsertLoc = $B9E; mapTrue = $FFFF; VAR varCode, WDEFRsrcID, wType: INTEGER; WDEFHandle: Handle; WDEFType: ResType; WDEFName: Str255; BEGIN varCode := GetWVariant(window); { Now, what about rDocProc types since their Variation } { Codes duplicate those of some standard types such as } { documentProc & dBoxProc. I could call: } { } { regionSize := WindowPeek(window)^.strucRgn^^.rgnSize; } { } { If regionSize = 10, then rgnBBox is rectangular; so } { if <> 10, we’ve got an rDocProc. HOWEVER, if the } { window is invisible because I’ve not yet called } { _ShowWindow, rgnBBox is empty and regionSize STILL } { equals 10. The solution is simple ... call } { GetWindowType when the window is being activated. } { Better yet ... the solution presented below, thanks } { to MacDTS, avoids this workaround. In addition, } { MacDTS’ solution avoids the pitfalls of “Murphy” } { inventing a totally new window type with a regionSize } { that dupes that of rDocProc. } WDEFHandle := Handle(QuickStrip(Ptr(WindowPeek(window)^.windowDefProc))); ; LoadResource(WDEFHandle); { May have been purged ... } { !! Thanks !!, Ben Cranston: } wordPtr(RomMapInsertLoc)^ := mapTrue; GetResInfo(WDEFHandle, WDEFRsrcID, WDEFType, WDEFName); wType := 16 * WDEFRsrcID + varCode; IF (wType = documentProc) | (wType = zoomDocProc) THEN hasGrowIcon := TRUE ELSE hasGrowIcon := FALSE; ; hasGrowIcon := hasGrowIcon | (ScrollHoriz(window) <> NIL) | (ScrollVert(window) <> NIL); GetWindowType := wType; END; { GetWindowType }
Another snipit goodie pertains to determining if we’re staring at a regular or a Desk Accessory Menu. I need this hummer because my “HandleCursor” routine is a part of my “doPeriodic” loop
{8} FUNCTION daMenu: BOOLEAN; { I know ... NEVER assume knowledge of Menu } { Record structures ... BUT ... } CONST MenuListLoc = $A1C; TYPE rMenuRec = RECORD menuOH: MenuHandle; menuLeft: INTEGER; { Left edge of Menu. } END; { rMenuRec } hMenuRec = RECORD menuHOH: MenuHandle; reserved: INTEGER; END; { hMenuRec } MenuList = RECORD lastMenu: INTEGER; { Offset to last regular } { MenuHandle. } lastRight: INTEGER; { Right edge of last } { Menu’s title. } mbResID: INTEGER; rMenu: ARRAY[0..0] OF rMenuRec; { The following fields are also present: } { } { lastHMenu: INTEGER; -- Offset from here to last } { hierarchical Menu. } { menuTitleSave: PixMapHandle; } { When my daMenu routine is called, there are NO } { hierarchical Menus: } { hMenu: ARRAY[0..0] OF hMenuRec; } END; { MenuList } MenuListPtr = ^MenuList; MenuListHdl = ^MenuListPtr; VAR MLHdl: MenuListHdl; nbrMenusX6, menuCounter, theMenuID: INTEGER; BEGIN MLHdl := MenuListHdl(longPtr(MenuListLoc)^); nbrMenusX6 := ORD(MLHdl^^.lastMenu); menuCounter := (nbrMenusX6 DIV 6) - 1; WHILE menuCounter >= 0 DO BEGIN theMenuID := MLHdl^^.rMenu[menuCounter].menuOH^^.menuID; { Watch out !!! with System 7 ... } { ... the Help Menu (kHMHelpMenuID = -16490) & } { ... the Application Menu (ID = ???) } IF (theMenuID < 0) & (theMenuID >= -16384) THEN Leave; menuCounter := menuCounter - 1; END; { scanning the MenuBar } { I could have used: } { daMenu := theMenuID < 0; } { because I KNOW my app has menus. However, to make } { this routine applicable to ANY app, what if ANY app } { had zip menus and there were no DA menus, then I } { would have to initialize with: } { theMenuID := 0; } { putting an extra statement in my time-critical } { “doPeriodic” loop. } daMenu := menuCounter >= 0; END; { daMenu }
Here comes the firehose full of code pertaining to Window MenuBars ... some of the stuff, such as MenuDefProc you folks will recognize as unchanged from James Matthews’ article while others I did change slightly (e.g., Jim’s MenuDefGlue). Anywho, folks, they are waiting for you on disk ...
This next routine is called by wInitMenus in order to sprinkle the Radius “magic” here and there:
{9} PROCEDURE InitBigScreen (VAR RadStatus: RadiusData; VAR fontSize: INTEGER); CONST largeMenuBar = 5; { Bit # in CPFlags field } { for non-MacII. } RadiusID = 0; VAR statusHdl, pivotHand: Handle; BEGIN SetResLoad(TRUE); pivotHand := PivotDSHand(GetNamedResource(‘INFO’, ‘Radius Pivot Display’)); IF pivotHand = NIL THEN LoadResource(pivotHand); RadStatus.PivotHdl := PivotDSHand(pivotHand); IF NOT aMac2 THEN statusHdl := GetNamedResource(‘INFO’, ‘Radius Display’) ELSE statusHdl := GetNamedResource(‘INFO’, ‘Radius II Display’); IF statusHdl = NIL THEN BEGIN LoadResource(statusHdl); IF statusHdl = NIL THEN {Still !!! } BEGIN RadStatus.radType := none; fontSize := normalSize; EXIT(InitBigScreen); END; { STILL! } END; { Zip } IF NOT aMac2 THEN BEGIN IF BTST(RadBWStatHdl(statusHdl)^^.CPFlags, largeMenuBar) THEN BEGIN RadBWStatHdl(statusHdl)^^.LargeFontEn := chr(1); AddResource(statusHdl, ‘INFO’, RadInfoID, ‘Radius Display’); fontSize := chicago16; { ID = 128*font number + size: } BIGfont := GetResource(‘FONT’, 128 * systemFont + chicago16); IF BIGfont = NIL THEN LoadResource(BIGfont); RadBWStatHdl(statusHdl)^^.LargeFontEn := chr(0); RadBWStatHdl(statusHdl)^^.PluggedIn:= chr(0); AddResource(statusHdl, ‘INFO’, RadInfoID, ‘Radius Display’); END ELSE fontSize := normalSize; ; RadStatus.radType := radBW; RadStatus.BWHdl := RadBWStatHdl(statusHdl); END ELSE { aMac2 } BEGIN IF ord(RadIIStatHdl(statusHdl)^^.LargeMenus) <> 0 THEN BEGIN fontSize := chicago16; BIGfont := GetResource(‘FONT’, chicago16); IF BIGfont = NIL THEN LoadResource(BIGfont); END ELSE fontSize := normalSize; ; RadStatus.radType := radII; RadStatus.IIHdl := RadIIStatHdl(statusHdl); END; END; { InitBigScreen }
Speaking of wInitMenus, note that we set the values of four parms with/without the Radius big screen monitors:
1) between titles -- see James Matthews’ article.
2) aboveBelowItem -- prevents half a menu item string showing for a vertically scrolling Menu.
3) menuFrame, menuShadow -- box around a pulled-down Menu.
scrollPolyDXY plays in wGetMSA. Jim discusses invertOverlap.
{10} FUNCTION wInitMenus: wMenuBarListHdl; { Call immediately after “InitManagers”: } { } { Creates a wMenuBarList and quantifies } { assorted & sordid global parms. } VAR wMBL: wMenuBarListHdl; BEGIN wMBL := wMenuBarListHdl(NewClearHandle(SizeOf(wMenuBarList))); ; IF MemError <> noErr THEN BEGIN wInitMenus := NIL; EXIT(wInitMenus); END; { Whoops !! } { wClearMenuBarList(wMBL); -- NOT needed here !! } wInitMenus := wMBL; { ++ stuff for BIGees: } InitBigScreen(FPDRsrc, sizeFont); IF sizeFont > normalSize THEN BEGIN betweenTitles := 18; aboveBelowItem := 3; menuFrame := 2 * frame; menuShadow := 2 * shadow; END ELSE { small potatoes } BEGIN betweenTitles := 14; aboveBelowItem := 2; menuFrame := frame; menuShadow := shadow; END; { ELSE } frameShad := menuFrame + menuShadow; scrollPolyDXY := mBarHt DIV 2; { Neighboring MENUs share inverted space: } invertOverlap := (betweenTitles DIV 2) + 1; END; { wInitMenus }
wGetNewMBar is called every time I create a new window. Please pay close attention to the code that addresses color. This is where I store the attached Menu resource’s Menu Color Table (MCTableHandle) into wMenuBarHandle^^.wMCTable. I need this info in order to draw the Menu’s colors.
{11} FUNCTION wGetNewMBar (wp: WindowPtr; wMenuBarID: INTEGER): wMenuBarHandle; { Pass wMenuBarID = noMBARrsrc if you wish to start fresh } { and call wInsertMenu yourself. } CONST none = 0; TYPE rMenuBar = RECORD numMenus: INTEGER; menuIDs: ARRAY[0..0] OF INTEGER; END; rMenuBarPtr = ^rMenuBar; rMenuBarHdl = ^rMenuBarPtr; VAR rMBar: rMenuBarHdl; mh: MenuHandle; i: INTEGER; theWorld: SysEnvRec; itDoesntMatter: OSErr; BEGIN wGetNewMBar := NIL; { Assume the pits !! } ; IF wp = NIL THEN EXIT(wGetNewMBar); rMBar := rMenuBarHdl(GetResource(‘MBAR’, wMenuBarID)); { Out with the old Window Menu Bar if there’s one } { and in with the new: } wDeleteWMB(wp); ; mBar := wMenuBarHandle(NewClearHandle(SizeOf(wMenuBar))); IF MemError <> noErr THEN BEGIN IF rMBar <> NIL THEN ReleaseResource(Handle(rMBar)); EXIT(wGetNewMBar); END; { Whoops !! } mBar^^.titleHilited := noneHilited; wSetMenuBar(mBar, wp); { & so sue me -- I’m paranoid !! } IF (rMBar <> NIL) & (rMBar^^.numMenus > 0) THEN BEGIN { Save & restore main Menu Color Table } { so that GetMenu does NOT change it. } IF aMac2 THEN oldMCTable := GetMCInfo; FOR i := 0 TO (rMBar^^.numMenus - 1) DO BEGIN mh := GetMenu(rMBar^^.menuIDs[i]); IF mh <> NIL THEN { Paranoid-ville again !! } BEGIN IF i = 0 THEN { = Apple Menu } BEGIN itDoesntMatter := SysEnvirons(1, theWorld); IF theWorld.systemVersion < $0700 THEN BEGIN SetItemIcon(mh, AboutItem, none); SetItemCmd(mh, AboutItem, char(none)); END; AddResMenu(mh, ‘DRVR’); { + DAs } END; { Apple Menu } wInsertMenu(mBar, mh, atEnd); { For calling GetMenu & AddResMenu lots. } DetachResource(Handle(mh)); END; { IF mh <> NIL } END; { FOR } ; ReleaseResource(Handle(rMBar)); IF aMac2 THEN BEGIN newMCTable := GetMCInfo; { Safe on the Stack !! } SetMCInfo(oldMCTable); { Save for drawing & selecting. } mbar^^.wMCTable := newMCTable; END; { IF aMac2} END; { IF a rMBar } wGetNewMBar := mBar; END; { wGetNewMBar }
wGetMenuBar is used to retrieve the given window’s wMenuBarHandle for subsequent feeding to wDrawMenuBar and wDrawMSA for Update and Activate Events.
{12} FUNCTION wGetMenuBar (wp: WindowPtr): wMenuBarHandle; VAR i: INTEGER; BEGIN found := false; i := 0; ; wGetMenuBar := NIL; IF mBarList = NIL THEN EXIT(wGetMenuBar); WITH mBarList^^ DO BEGIN IF numWindows > 0 THEN WHILE (NOT found) & (i < numWindows) DO IF WMBars[i]^^.wp = wp THEN found := true ELSE i := i + 1 { End of WHILE } ELSE { zip windows } ; IF found THEN wGetMenuBar := WMBars[i]; END; { WITH } END; { wGetMenuBar } Just as James Matthews’ did it: PROCEDURE wSetMenuBar (theMenuBar: wMenuBarHandle; wp: WindowPtr); BEGIN theMenuBar^^.wp := wp; { Simple, aint it ?!!? } END; { wSetMenuBar } For example: windPtr := GetNewWindow(...); menuBar := wGetNewMBar(windPtr, barID); IF menuBar <> NIL THEN wAddWMB(menuBar); PROCEDURE wAddWMB (theMenuBar: wMenuBarHandle); VAR i: INTEGER; BEGIN newBarListSize := SizeOf(wMenuBarList) + (mBarList^^.numWindows + 1) * 4; IF newBarListSize > GetHandleSize(Handle(mBarList)) THEN IF MemError = noErr THEN SetHandleSize(Handle(mBarList), newBarListSize); IF MemError = noErr THEN WITH mBarList^^ DO BEGIN WMBars[numWindows] := theMenuBar; numWindows := numWindows + 1 END; { WITH } END; { wAddWMB }
Used when closing a window:
{13} PROCEDURE wDeleteWMB (wp: WindowPtr); VAR i, j: INTEGER; BEGIN found := false; i := 0; IF mBarList^^.numWindows > 0 THEN BEGIN WHILE (NOT found) & (i < mBarList^^.numWindows) DO IF mBarList^^.WMBars[i]^^.wp = wp THEN found := true ELSE i := i + 1; { End of WHILE } IF found THEN BEGIN wClearMenuBar(mBarList^^.WMBars[i]); DisposHandle(Handle(mBarList^^.WMBars[i])); { Not the last one in the List. } IF i <> (mBarList^^.numWindows - 1) THEN FOR j := (i + 1) TO (mBarList^^.numWindows - 1) DO mBarList^^.WMBars[j - 1] := mBarList^^.WMBars[j] ELSE { Delete the last one. Already done, } { so nada required here. } ; mBarList^^.numWindows := mBarList^^.numWindows - 1; newBarListSize := GetHandleSize(Handle(mBarList)) - 4; IF MemError = noErr THEN SetHandleSize(Handle(mBarList), newBarListSize); END; { IF found } END { IF numWindows >0 } ELSE { zip windows } ; END; { wDeleteWMB }
To tell you the truth, I do NOT even use wClearMenuBarList, but it’s here for the sake of symmetry with wDrawMenuBar.
{14} PROCEDURE wClearMenuBarList (theMenuBarList: wMenuBarListHdl); { For now, I do NOT use the passed parm because it’s a } { global. This may change in the future, however. } CONST WindowListLoc = $9D6; { 1st window in linked list. } VAR window: WindowPeek; BEGIN window := WindowPeek(longPtr(WindowListLoc)); WHILE window <> NIL DO BEGIN wDeleteWMB(WindowPtr(window)); window := window^.nextWindow; END; { WHILE } { The following has already happened after execution } { of above WHILE loop: } { } { theMenuBarList^^.numWindows := 0; } { SetHandleSize(Handle(theMenuBarList), } { SizeOf(wMenuBarList)); } END; { wClearMenuBarList }
These two routines are called by wInsertMenu & wDeleteMenu, respectively.
{15} FUNCTION IncreaseSize (theMenuBar: wMenuBarHandle): OSErr; VAR hState: SignedByte; BEGIN newWMBSize := SizeOf(wMenuBar) + (theMenuBar^^.numMenus + 1) * SizeOf(wMenuRec); IF newWMBSize > GetHandleSize(Handle(theMenuBar)) THEN IF MemError = noErr THEN BEGIN hState := HGetState(Handle(theMenuBar)); HUnlock(Handle(theMenuBar)); SetHandleSize(Handle(theMenuBar), newWMBSize); { Just in case entry state is locked: } MoveHHi(Handle(theMenuBar)); HSetState(Handle(theMenuBar), hState); END; ; IncreaseSize := MemError; END; { IncreaseSize } FUNCTION DecreaseSize (theMenuBar: wMenuBarHandle): OSErr; VAR hState: SignedByte; BEGIN newWMBSize := SizeOf(wMenuBar) + (theMenuBar^^.numMenus - 1) * SizeOf(wMenuRec); IF newWMBSize < GetHandleSize(Handle(theMenuBar)) THEN IF MemError = noErr THEN BEGIN hState := HGetState(Handle(theMenuBar)); HUnlock(Handle(theMenuBar)); SetHandleSize(Handle(theMenuBar), newWMBSize); MoveHHi(Handle(theMenuBar)); HSetState(Handle(theMenuBar), hState); END; ; DecreaseSize := MemError; END; { DecreaseSize }
Called when inserting & deleting a Menu so the Menu’s titleRect can be quantified.
{16} FUNCTION GetTitleWidth (theMenu: MenuHandle): INTEGER; VAR oldTxSize, oldTxFont, oldTxMode: INTEGER; oldTxStyle: Style; BEGIN oldTxSize := thePort^.txSize; oldTxFont := thePort^.txFont; oldTxStyle := thePort^.txFace; TextSize(sizeFont); TextFont(systemFont); TextFace([]); ; GetTitleWidth := StringWidth(theMenu^^.menuData); ; TextSize(oldTxSize); TextFont(oldTxFont); TextFace(oldTxStyle); END; { GetTitleWidth } This routine is present for the same reason as aboveBelowItem as discussed above. See wMenuSelect. FUNCTION GetItemIconSize (theMenu: MenuHandle; item: INTEGER): INTEGER; CONST reducedIconCmd = $1D; smallIconCmd = $1E; VAR iconNbr: Byte; sizeIcon: INTEGER; theCICN: CIconHandle; cmdChar: char; BEGIN GetItemIcon(theMenu, item, iconNbr); IF iconNbr = 0 THEN sizeIcon := 0 ELSE BEGIN IF aMac2 THEN BEGIN theCICN := GetCIcon(iconNbr + 256); IF theCICN <> NIL THEN BEGIN WITH theCICN^^.iconPMap.bounds DO sizeIcon := bottom - top; DisposCIcon(theCICN); END ELSE { no cicn } sizeIcon := 32; END { aMac2 } ELSE sizeIcon := 32; GetItemCmd(theMenu, item, cmdChar); IF (cmdChar = chr(reducedIconCmd)) | (cmdChar = chr(smallIconCmd)) THEN sizeIcon := sizeIcon DIV 2; END; { Has either an ICON, a CICN, a reduced icon or a SICN } GetItemIconSize := sizeIcon; END; { GetItemIconSize }
Continued in next frame | ||
Volume Number: | 7 | |
Issue Number: | 10 | |
Column Tag: | Color Workshop |
Related Info: Menu Manager Memory Manager Color Quickdraw
Window Menu Bars Revisited (code)
When I insert a hierarchical Menu, I do so after all regular Menus. Also note that I account for the fact that all Menu items #32 and beyond are considered enabled as stipulated by the Menu Manager.
The arithmetic for determining the titleRect for a regular Menu dittos James Matthews’ algorithm. The titleRect for a hierarchical Menu is simply the rect for the parent item. We’ve just seen a picture worth at least two pages of prose ...
Note below that I store menuType and parentID in the wMenuRec RECORD. For a regular Menu, parentID = 0. I need these two pieces of info so I can subsequently determine what Menu type I’m looking at and, if a hierarchical Menu, so I can back-calculate to the Menu’s enclosing frame (see RedrawParentItem within wMenuSelect).
{17} PROCEDURE wInsertMenu (theMenuBar: wMenuBarHandle; theMenu: MenuHandle; beforeID: INTEGER); { Insert a Menu into a defined wMenuBar: } VAR hierID, index, i, j: INTEGER; found, enabled: BOOLEAN; parentMenuHdl: MenuHandle; oldPort: GrafPtr; cmdChar, parentMark: char; parentItemRect, parentFrameRect: Rect; currentHeight, prevHeight, sizeIcon: INTEGER; BEGIN IF beforeID = atEnd THEN BEGIN i := 0; WHILE (i < theMenuBar^^.numMenus) & (theMenuBar^^.wMenus[i].menuType = regMenu) DO i := i + 1; IF i < theMenuBar^^.numMenus THEN { Insert after end of regular portion of Window Menu Bar, } { which occurs just prior to the Hierarchical portion. } BEGIN wInsertMenu(theMenuBar, theMenu, theMenuBar^^.wMenus[i].mh^^.menuID); EXIT(wInsertMenu); END ELSE BEGIN IF IncreaseSize(theMenuBar) <> noErr THEN EXIT(wInsertMenu); titleWidth := GetTitleWidth(theMenu); END; { ELSE: i = numMenus } END { beforeID = atEnd } ELSE IF beforeID = hierMenu THEN BEGIN hierID := theMenu^^.menuID; i := 0; found := false; ; { Scan entire wMenuBar. } WHILE (i < theMenuBar^^.numMenus) & NOT found DO BEGIN parentMenuHdl := theMenuBar^^.wMenus[i].mh; { Scan each menu. } FOR j := 1 TO CountMItems(parentMenuHdl) DO BEGIN GetItemCmd(parentMenuHdl, j, cmdChar); IF cmdChar = char(hMenuCmd) THEN BEGIN GetItemMark(parentMenuHdl, j, parentMark); IF ord(parentMark) = hierID THEN BEGIN found := true; Leave; { FOR loop } END; { IF ord() } END; { Hierarchical Menu } END; { FOR } i := i + 1; { IF found, i = correct # plus 1. } END; { WHILE } IF found THEN { ... is it enabled ??? } IF j < 32 THEN enabled := BitTst(@theMenuBar^^.wMenus[i - 1].mh^^.enableFlags, menuTitleBit - j) ELSE enabled := true; { Items 32 & beyond. } IF found & enabled & (IncreaseSize(theMenuBar) = noErr) THEN BEGIN { ’tis re-locked by IncreaseSize PROC. } WITH theMenuBar^^, wMenus[numMenus] DO BEGIN mh := theMenu; { Place at end = Hierarchical portion. } menuType := hierMenu; parentID := parentMenuHdl^^.menuID; { These fields are filled-in later by wMenuSelect } { for both Hierarchical and Regular menus: } { } { menuParentRgn, cumParentRgn and MenuDownOSHdl } { } { You’ll notice that I ignore these fields when } { inserting and deleting a Regular menu because } { Hierarchical menus are inserted AFTER all Regular } { menus and are deleted immediately after use. } END; { WITH theMenuBar^^, wMenus[numMenus] } { titleRect = parent item’s rect. } { } { For starters, setup left & right coordinates: } WITH parentItemRect DO BEGIN { [i-1] belongs to parentMenuHdl. } WITH theMenuBar^^.wMenus[i - 1] DO IF menuType = regMenu THEN left := titleRect.left + menuFrame ELSE left := titleRect.right - 4 * menuFrame + menuFrame; CalcMenuSize(parentMenuHdl); right := left + parentMenuHdl^^.menuWidth; END; { WITH parentItemRect } WITH theMenuBar^^.wMenus[i - 1] DO IF menuType = regMenu THEN currentHeight := titleRect.bottom + menuFrame ELSE currentHeight := titleRect.top + menuFrame; FOR index := 1 TO j DO BEGIN prevHeight := currentHeight; sizeIcon := GetItemIconSize(parentMenuHdl, index); currentHeight := currentHeight + aboveBelowItem + Max(sizeFont, sizeIcon) + aboveBelowItem; END; { FOR } { Complete the other two dimensions. } WITH parentItemRect DO BEGIN top := prevHeight; bottom := currentHeight; END; { WITH parentItemRect } WITH theMenuBar^^, wMenus[i - 1] DO BEGIN { Change ONLY right & left coords ... } parentFrameRect := parentItemRect; WITH parentFrameRect DO BEGIN left := left - menuFrame; right := right + menuFrame + menuShadow; END; { WITH parentFrameRect } GetPort(oldPort); { ---- } SetPort(wp); LocalGlobal(parentFrameRect); { WMgrPort’s portBits.bounds in global coordinates: } WITH gScreen DO BEGIN IF parentFrameRect.right > right - 2 * menuFrame THEN { Shift left. } IF menuType = regMenu THEN OffsetRect(parentFrameRect, right - 2 * menuFrame - parentFrameRect.right, 0) ELSE OffsetRect(parentFrameRect, -(titleRect.right - titleRect.left - 8 * menuFrame + (parentFrameRect.right - parentFrameRect.left)), 0); ; IF parentFrameRect.left < left + 2 * menuFrame THEN { Shift right. } OffsetRect(parentFrameRect, left + 2 * menuFrame - parentFrameRect.left, 0); END; { WITH gScreen } { Back to local window coordinates. } GlobalLocal(parentFrameRect); { ---- } SetPort(oldPort); parentItemRect := parentFrameRect; WITH parentItemRect DO BEGIN left := left + menuFrame; right := right - menuFrame - menuShadow; END; ; { [numMenus] belongs to inserted sub-Menu: } wMenus[numMenus].titleRect := parentItemRect; numMenus := numMenus + 1; END; { WITH theMenuBar^^, wMenus[i - 1] } END; { IF found & enabled & IncreaseSize } EXIT(wInsertMenu); END { beforeID = hierMenu } ELSE { Smack in the middle somewhere !! } BEGIN IF IncreaseSize(theMenuBar) <> noErr THEN EXIT(wInsertMenu); titleWidth := GetTitleWidth(theMenu); WITH theMenuBar^^ DO BEGIN i := 0; WHILE (i < numMenus) & (wMenus[i].mh^^.menuID <> beforeID) DO i := i + 1; ; IF i <> numMenus THEN BEGIN FOR j := numMenus DOWNTO (i + 1) DO BEGIN wMenus[j].mh := wMenus[j - 1].mh; wMenus[j].menuType := wMenus[j - 1].menuType; wMenus[j].parentID := wMenus[j - 1].parentID; { Overkill just to copy top & bottom ... } wMenus[j].titleRect := wMenus[j - 1].titleRect; { ... now, adjust right & left if a regular Menu. } IF wMenus[j].menuType = regMenu THEN OffsetRect(wMenus[j].titleRect, titleWidth + betweenTitles, 0); END; { FOR j := numMenus DOWNTO (i + 1) } END; { IF i <> numMenus } END; { WITH } END; { in middle } { Some wierd arithmetic ... } WITH theMenuBar^^, wMenus[i].titleRect DO BEGIN top := wp^.portRect.top; ; bottom := top + mBarHt - menuFrame; ; left := wp^.portRect.left; { Make room for Menu Scroll Activator -- see “wDrawMSA”: } IF i = 0 THEN left := left + 2 * scrollPolyDXY + menuFrame + betweenTitles - invertOverlap ELSE left := left + wMenus[i - 1].titleRect.right - invertOverlap + betweenTitles - invertOverlap; ; right := left + invertOverlap + titleWidth + invertOverlap; IF numMenus = 0 THEN { 1st call to wInsertMenu. } barLength := betweenTitles + titleWidth + betweenTitles ELSE barLength := barLength + titleWidth + betweenTitles; wMenus[i].mh := theMenu; wMenus[i].menuType := regMenu; wMenus[i].parentID := mainMenu; numMenus := numMenus + 1; END; { WITH } END; { wInsertMenu }
wDeleteMenu simply reverses the effect of wInsertMenu.
{18} PROCEDURE wDeleteMenu (theMenuBar: wMenuBarHandle; menuID: INTEGER); VAR i, j: INTEGER; BEGIN i := 0; WITH theMenuBar^^ DO WHILE (i < numMenus) & (wMenus[i].mh^^.menuID <> menuID) DO i := i + 1; { End of 1st WITH } ; IF i <> theMenuBar^^.numMenus THEN BEGIN IF theMenuBar^^.wMenus[i].menuType = regMenu THEN titleWidth := GetTitleWidth(theMenuBar^^.wMenus[i].mh); WITH theMenuBar^^ DO BEGIN IF wMenus[i].menuType = regMenu THEN IF numMenus = 1 THEN { Soon to be zero. } barLength := 0 ELSE barLength := barLength - titleWidth - betweenTitles; FOR j := (i + 1) TO (numMenus - 1) DO BEGIN wMenus[j - 1].mh := wMenus[j].mh; wMenus[j - 1].menuType := wMenus[j].menuType; wMenus[j - 1].parentID := wMenus[j].parentID; wMenus[j - 1].titleRect := wMenus[j].titleRect; IF wMenus[j - 1].menuType = regMenu THEN OffsetRect(wMenus[j - 1].titleRect, -(titleWidth + betweenTitles), 0); END; { FOR j := (i + 1) TO (numMenus - 1) } END; { 2nd WITH } IF DecreaseSize(theMenuBar) = noErr THEN ; theMenuBar^^.numMenus := theMenuBar^^.numMenus - 1; END; { IF i <> theMenuBar^^.numMenus } END; { wDeleteMenu }
I use GetWBMrects primarily to quantify the rects containing my MSAs which, in turn, play in my DoMouseDown PROC.
{19} PROCEDURE GetWBMrects (wp: WindowPtr; VAR WBMrect, leftMSArect, rightMSArect: Rect); PROCEDURE ZeroRect (VAR r: Rect); BEGIN WITH r DO BEGIN top := 0; left := 0; bottom := 0; right := 0; END; { WITH } END; { ZeroRect } BEGIN { GetWBMrects } IF wGetMenuBar(wp) <> NIL THEN BEGIN { + quantifies “hasGrowIcon” for update Event: } windType := GetWindowType(wp); WBMrect := wp^.portRect; WITH WBMrect DO BEGIN IF hasGrowIcon THEN right := right - (scrollWidth - frame); bottom := top + mBarHt; END; leftMSArect := WBMrect; WITH leftMSArect DO right := left + 2 * scrollPolyDXY + menuFrame; rightMSArect := WBMrect; WITH rightMSArect DO left := right - 2 * scrollPolyDXY - menuFrame; END { window has a Window Bar Menu } ELSE BEGIN ZeroRect(WBMrect); ZeroRect(leftMSArect); ZeroRect(rightMSArect); END; { ELSE } dynamicBalloons[0].dynamicStrID := 128; IF windType = rDocProc THEN dynamicBalloons[0].dynamicStrIndex := 3 ELSE dynamicBalloons[0].dynamicStrIndex := 6; dynamicBalloons[0].dynamicR := leftMSArect; ; dynamicBalloons[1].dynamicStrID := 128; IF windType = rDocProc THEN dynamicBalloons[1].dynamicStrIndex := 2 ELSE dynamicBalloons[1].dynamicStrIndex := 5; dynamicBalloons[1].dynamicR := WBMrect; { Between MSAs:. } InsetRect(dynamicBalloons[1].dynamicR, leftMSArect.right, 0); ; dynamicBalloons[2].dynamicStrID := 128; IF windType = rDocProc THEN dynamicBalloons[2].dynamicStrIndex := 4 ELSE dynamicBalloons[2].dynamicStrIndex := 7; dynamicBalloons[2].dynamicR := rightMSArect; END; { GetWBMrects }
GetColors, RestoreColors and wSetColorMenu play with color Menus and all three are called by both wDrawMenuBar and wMenuSelect:
{20} PROCEDURE GetColors; BEGIN IF aMac2 THEN BEGIN GetBackColor(oldBackColor); GetForeColor(oldForeColor); END; { IF } END; { GetColors } PROCEDURE RestoreColors; BEGIN IF aMac2 THEN BEGIN RGBBackColor(oldBackColor); RGBForeColor(oldForeColor); END; { IF } END; { RestoreColors } PROCEDURE wSetColorMenu (menusID, itemsID: INTEGER); { Without redefining Apple’s built-in MenuDefProc, } { we’re restricting ourselves to ALL Item text } { (including the mark & CMD-key) being he same } { color and ditto for the background color of ALL } { Items. } VAR MCTBEntry: MCEntryPtr; { What we actually got back from GetMCTBEntry } { compared with what we asked for, the latter } { being the original parms passed to wSetColorMenu: } returnedMenuID, returnedMenuItem: INTEGER; FUNCTION GetMCTBEntry (VAR menuID, itemID: INTEGER): MCEntryPtr; VAR mctbEntryPtr: MCEntryPtr; BEGIN GetMCTBEntry := NIL; { NOT very optimistic, are we ?!!? } mctbEntryPtr := GetMCEntry(menuID, itemID); IF mctbEntryPtr = NIL THEN { Could NOT find what we asked for, so recurse } { UP the chain: } { } { if no specified Item entry, then look for } { Title entry } { if no asked-for Title entry, then look for } { MenuBar entry } { if no specified MenuBar entry, then our } { ‘mctb’ resource is NOT correct } BEGIN IF (menuID <> mceMenuBar) & (itemID <> mceMenuTitle) THEN BEGIN { Asked for item } itemID := mceMenuTitle; GetMCTBEntry := GetMCTBEntry(menuID, itemID); END ELSE IF (menuID <> mceMenuBar) & (itemID = mceMenuTitle) THEN BEGIN { Asked for title } menuID := mceMenuBar; itemID := mceMenuBar; GetMCTBEntry := GetMCTBEntry(menuID, itemID); END ELSE IF (menuID = mceMenuBar) & (itemID = mceMenuBar) THEN ; { Ran out of steam !!! } END ELSE GetMCTBEntry := mctbEntryPtr; END; { GetMCTBEntry } BEGIN { wSetColorMenu } IF NOT aMac2 THEN EXIT(wSetColorMenu); { VARed ... what we actually get back from GetMCTBEntry. } returnedMenuID := menusID; returnedMenuItem := itemsID; ; MCTBEntry := GetMCTBEntry(returnedMenuID, returnedMenuItem); IF MCTBEntry = NIL THEN EXIT(wSetColorMenu); WITH MCTBEntry^ DO IF (returnedMenuID <> mceMenuBar) & (returnedMenuItem <> mceMenuTitle) THEN BEGIN { Asked for AND got a Menu Item. } RGBBackColor(mctRGB4); RGBForeColor(mctRGB2); END { an Item element} ELSE IF (returnedMenuID <> mceMenuBar) & (returnedMenuItem = mceMenuTitle) THEN BEGIN RGBBackColor(mctRGB4); IF itemsID = mceMenuTitle THEN { Asked for AND got a Menu Title. } RGBForeColor(mctRGB1) ELSE { Asked for an Item, recursed to a Title. } RGBForeColor(mctRGB3); END { a Title element } ELSE IF (returnedMenuID = mceMenuBar) & (returnedMenuItem = mceMenuBar) THEN BEGIN { Here, if you ask for the MenuBar entry, I assume } { you’re just interested in the background color } { because you’re coloring the overall MenuBar via } { _EraseRect. If you’re really interested in the } { background color of the pulled-down Menu, then } { ask for a Menu Title or a Menu Item entry. } IF menusID = mceMenuBar THEN { Got what we asked for !! } RGBBackColor(mctRGB4) ELSE IF itemsID = mceMenuTitle THEN { Asked for a Title, recursed to MenuBar. } BEGIN RGBBackColor(mctRGB2); RGBForeColor(mctRGB1); END ELSE { Asked for an Item, recursed TWICE to MenuBar. } BEGIN RGBBackColor(mctRGB2); RGBForeColor(mctRGB3); END END; { got back a MenuBar element} { End of WITH } END; { wSetColorMenu }
Note that I lock theMenuBar passed to wDrawMenuBar, but do NOT have that luxury with wMenuSelect. The reason for the latter is because I recurse however many times it takes to get to the appropriate hierarchical Menu no matter how deep it’s buried. More on this recursing & cursing later ...
{21} PROCEDURE wDrawMenuBar (theMenuBar: wMenuBarHandle); { Draw a wMenuBar with appropriate highlighting: } LABEL 100, 200; VAR active: BOOLEAN; y0, i: INTEGER; lsWBMrect, offBarRect, titleR, visDrawingRect: Rect; titleRgn: RgnHandle; { Keep safe on Stack to avoid Memory Manager blues ... } safeTitle: Str255; BEGIN IF theMenuBar^^.numMenus = 0 THEN { Nada !! } EXIT(wDrawMenuBar); mbHState := HGetState(Handle(theMenuBar)); MoveHHi(Handle(theMenuBar)); { ... because some TRAPs called inside the } { WITH block move memory. } HLock(Handle(theMenuBar)); GetPort(oldPort); SetPort(theMenuBar^^.wp); oldClip := NewRgn; GetClip(oldClip); ; GetColors; IF aMac2 THEN BEGIN oldMCTable := GetMCInfo; SetMCInfo(theMenuBar^^.wMCTable); END; { IF aMac2 } { GetWBMrects already called by wDrawMSA. } lsWBMrect := WBMrect; LocalGlobal(lsWBMrect); { Local window coordinates are input to & local screen } { coordinates are returned from CreateOffScreen: } WITH theMenuBar^^ DO BEGIN active := (ORD(WindowPeek(wp)^.hilited) <> 0); { offBarRect may end up being wider than window’s portRect. } WITH offBarRect DO BEGIN top := WBMrect.top; bottom := WBMrect.bottom - menuFrame; left := leftMSArect.right; right := left + barLength; IF right < rightMSArect.left THEN right := rightMSArect.left; END; { WITH offBarRect } IF barOSHdl <> NIL THEN BEGIN LocalGlobal(offBarRect); WITH lScreen.bounds DO { _GlobalToLocal } OffsetRect(offBarRect, left, top); GOTO 100; END; { Still there } barOSHdl := CreateOffScreen(offBarRect); IF (QuickStrip(Ptr(barOSHdl)) = NIL) | (barOSHdl^^.CreateOffScreenError <> noErr) THEN GOTO 200; { ELSE draw into off-screen Port as follows: } TextSize(sizeFont); TextFont(systemFont); TextFace([]); TextMode(srcOr); { We canNOT call _GlobalToLocal here because we’ve } { changed the portBits.bounds rectangle of our } { off-screen port within “CreateOffScreen”. } WITH lScreen.bounds DO { Bounds BEFORE change. } OffsetRect(lsWBMrect, left, top); ; ClipRect(offBarRect); wSetColorMenu(mceMenuBar, mceMenuBar); EraseRect(offBarRect); { Eliminate all stray matter. } y0 := (mBarHt - menuFrame - sizeFont) DIV 2; ; FOR i := 0 TO (numMenus - 1) DO BEGIN IF wMenus[i].menuType = hierMenu THEN Cycle; titleR := wMenus[i].titleRect; { Convert to local screen coordinates: } OffsetRect(titleR, lsWBMrect.left, lsWBMrect.top); {Eliminate the overshoot placed there to invert title,} {because all we wish to do is draw the title string: } InsetRect(titleR, invertOverlap, 0); ; MoveTo(titleR.left, titleR.bottom - y0); wSetColorMenu(wMenus[i].mh^^.menuID, mceMenuTitle); safeTitle := wMenus[i].mh^^.menuData; DrawString(safeTitle); {Gray-out disabled Menu titles ONLY if window is active.} {We dim the whole Window Bar Menu below if window isn’t.} {The latter allows for CMD-dragging the window. } IF active & NOT BitTst(@wMenus[i].mh^^.enableFlags, menuTitleBit) THEN BEGIN titleRgn := NewRgn; RectRgn(titleRgn, titleR); DimRgn(titleRgn); DisposeRgn(titleRgn); END; { Gray-out disabled title } END; { FOR ... drawing off-screen } 100: ToOnScreen(barOSHdl); { Back to “Square 1”. } WITH barOSHdl^^ DO BEGIN visDrawingRect := drawingRect; ClipRect(drawingRect); BackColor(whiteColor); { So funny colorization } ForeColor(blackColor); { does NOT happen !! } ; { Adjust if overlapping right MSA. } WITH visDrawingRect DO IF right > rightMSArect.left THEN right := rightMSArect.left; onScreenRgn := NewRgn; RectRgn(onScreenRgn, visDrawingRect); CalcVis(WindowPeek(wp)); { Radius’ TOM INIT. } SectRgn(onScreenRgn, wp^.visRgn, onScreenRgn); CopyBits(offBitMapPtr^, thePort^.portBits, offBarRect, drawingRect, srcCopy, onScreenRgn); { Consider you’re CMD-dragging ... } IF NOT active THEN DimRgn(onScreenRgn); ValidRgn(onScreenRgn); DisposeRgn(onScreenRgn); END; { WITH barOSHdl^^ } 200: DisposOffScreen(barOSHdl); END; { WITH theMenuBar^^ } { Deliberately last ... we’re safe now !! } HSetState(Handle(theMenuBar), mbHState); RestoreColors; IF aMac2 THEN SetMCInfo(oldMCTable); ; SetClip(oldClip); DisposeRgn(oldClip); SetPort(oldPort); { Appropriate highlighting = none. } wHiliteMenu(theMenuBar, 0); END; { wDrawMenuBar }
Here’s another PROC wherein I need access to my MSA rects. When I scroll, I merely offset the titleRects of the regular Menu’s titleRects (the hierarchical Menus and their titleRects are NOT present except when I’m choosing a Menu item within wMenuSelect). After offseting the titleRects, I redraw the MSAs and then the window’s MenuBar ... simple ain’t it ?!*!?
{22} PROCEDURE wScrollMenuBar (theMenuBar: wMenuBarHandle); CONST toRight = 1; toLeft = -1; VAR mouseLoc: Point; nbrMenus, direction, temp, deltaScroll, i: INTEGER; BEGIN nbrMenus := theMenuBar^^.numMenus; GetMouse(mouseLoc); IF PtInRect(mouseLoc, leftMSArect) THEN direction := toRight ELSE IF PtInRect(mouseLoc, rightMSArect) THEN direction := toLeft ELSE EXIT(wScrollMenuBar); { Should NOT happen !! } WHILE WaitMouseUp DO BEGIN IF direction = toRight THEN BEGIN temp := leftMSArect.right + betweenTitles - invertOverlap - mBar^^.wMenus[0].titleRect.left; IF temp <= 0 THEN Leave ELSE deltaScroll := Min(StringWidth(‘A’), temp); END ELSE { direction = toLeft } BEGIN temp := rightMSArect.left - (mBar^^.wMenus[nbrMenus - 1].titleRect.right - invertOverlap + betweenTitles); IF temp >= 0 THEN Leave ELSE { Absolute value. } deltaScroll := Min(StringWidth(‘A’), -temp); END; deltaScroll := direction * deltaScroll; FOR i := 0 TO (nbrMenus - 1) DO OffsetRect(theMenuBar^^.wMenus[i].titleRect, deltaScroll, 0); wDrawMSA(theMenuBar); wDrawMenuBar(theMenuBar); END; { WHILE } END; { wScrollMenuBar } Get ’em and weep, I mean ‘draw’ : PROCEDURE wGetMSA (theMenuBar: wMenuBarHandle); VAR dx, dy, over, back, up, down: INTEGER; tempPoly: PolyHandle; BEGIN WITH WBMrect DO BEGIN GetPort(oldPort); SetPort(theMenuBar^^.wp); GetWBMrects(theMenuBar^^.wp, WBMrect, leftMSArect, rightMSArect); { ---------------------------------------------------------------------- } { Get MSA at left of Window Bar Menu: } dx := 3 * (scrollPolyDXY DIV 2); dy := (bottom - menuFrame - top - scrollPolyDXY) DIV 2; over := scrollPolyDXY; back := -over; up := -scrollPolyDXY; down := scrollPolyDXY DIV 2; IF theMenuBar^^.leftScrollPoly = NIL THEN BEGIN { I can’t believe I did this ?!*!? } { theMenuBar^^.leftScrollPoly := OpenPoly; } tempPoly := OpenPoly; theMenuBar^^.leftScrollPoly := tempPoly; MoveTo(left + dx, bottom - menuFrame - dy); Line(0, up); { Vertical part ... } Line(back, down); { ... horizontal parts: } Line(over, down); ClosePoly; END; { Doesn’t exist, so make a new one } { ------------------------------------------------------------------------ } { Get MSA at right of Window Bar Menu: } IF theMenuBar^^.rightScrollPoly = NIL THEN BEGIN tempPoly := OpenPoly; theMenuBar^^.rightScrollPoly := tempPoly; MoveTo(right - dx, bottom - menuFrame - dy); Line(0, up); { Vertical part ... } Line(over, down); { ... horizontal parts: } Line(back, down); ClosePoly; END; { IF } SetPort(oldPort); END; { WITH WBMrect } END; { wGetMSA } PROCEDURE wDrawMSA (theMenuBar: wMenuBarHandle); { ... AND the bottom line bordering the Window Bar Menu. } LABEL 100; VAR dx, dy: INTEGER; aux: BOOLEAN; frameColor: RGBColor; BEGIN WITH WBMrect DO BEGIN GetPort(oldPort); SetPort(theMenuBar^^.wp); GetPenState(pnState); { ------------------------------------------------------------------------------------------------------------ } { Get the Window Bar Menu rect & draw a line beneath it: } wGetMSA(theMenuBar); { Calls GetWBMrects. } gWBMrect := WBMrect; LocalGlobal(gWBMrect); IF NOT SectRect(gWBMrect, gScreen, visRect) THEN GOTO 100; GlobalLocal(visRect); onScreenRgn := NewRgn; RectRgn(onScreenRgn, visRect); SectRgn(onScreenRgn, theMenuBar^^.wp^.visRgn, onScreenRgn); oldClip := NewRgn; GetClip(oldClip); SetClip(onScreenRgn); ; aux := GetWindowPartColor(theMenuBar^^.wp, wFrameColor, frameColor); IF (colorDepth > 1) & aux THEN BEGIN GetForeColor(oldForeColor); RGBForeColor(frameColor); END; { Draw in color } ; PenNormal; PenSize(menuFrame, menuFrame); MoveTo(left, bottom - menuFrame); Line(right - left, 0); { ---------------------------------------------------------------- } { Draw MSA at left of Window Menu: } IF theMenuBar^^.wMenus[0].titleRect.left < leftMSArect.right + betweenTitles - invertOverlap THEN FillPoly(theMenuBar^^.leftScrollPoly, black) ELSE BEGIN FillPoly(theMenuBar^^.leftScrollPoly, white); FramePoly(theMenuBar^^.leftScrollPoly); END; { ------------------------------------------------------------------------------------------------------ } { Draw vertical line separating MSA from Menu titles: } MoveTo(left + 2 * scrollPolyDXY, bottom - menuFrame); Line(0, -(bottom - top - menuFrame)); { -------------------------------------------------------------------------- } { Draw MSA at right of Window Bar Menu: } IF theMenuBar^^.wMenus[theMenuBar^^.numMenus - 1].titleRect.right - invertOverlap + betweenTitles > rightMSArect.left THEN FillPoly(theMenuBar^^.rightScrollPoly, black) ELSE BEGIN FillPoly(theMenuBar^^.rightScrollPoly, white); FramePoly(theMenuBar^^.rightScrollPoly); END; ; MoveTo(right - 2 * scrollPolyDXY - menuFrame, bottom - menuFrame); { + vertical } Line(0, -(bottom - top - menuFrame)); { line. } IF ORD(WindowPeek(theMenuBar^^.wp)^.hilited) = 0 THEN DimRgn(onScreenRgn); ValidRgn(onScreenRgn); DisposeRgn(onScreenRgn); SetClip(oldClip); DisposeRgn(oldClip); IF (colorDepth > 1) & aux THEN RGBForeColor(oldForeColor); 100: SetPenState(pnState); SetPort(oldPort); ValidRect(visRect); END; { WITH } END; { wDrawMSA }
Kill the polyHandles. Note that barOSHdl is always = NIL because immediately after I create this OffScreenRecHdl within wDrawMenuBar and subsequently bit it back on-screen, I then call DisposOffscreen.
{23} PROCEDURE wClearMenuBar (theMenuBar: wMenuBarHandle); { Called internally by wDeleteWMB to } { kiss some Handles goodbye. } BEGIN mbHState := HGetState(Handle(theMenuBar)); MoveHHi(Handle(theMenuBar)); HLock(Handle(theMenuBar)); WITH theMenuBar^^ DO BEGIN wHiliteMenu(theMenuBar, 0); { So sue me ... I’m paranoid !! } IF leftScrollPoly <> NIL THEN BEGIN KillPoly(leftScrollPoly); leftScrollPoly := NIL; { Mark as gone ... } END; ; IF rightScrollPoly <> NIL THEN BEGIN KillPoly(rightScrollPoly); rightScrollPoly := NIL; END; IF barOSHdl <> NIL THEN DisposOffScreen(barOSHdl); { Sets barOSHdl := NIL } END; { WITH } HSetState(Handle(theMenuBar), mbHState); END; { wClearMenuBar }
Now, the recursing & cursing ...
The local routine ItDoesFit is used when you’ve dragged the window close to the screen’s phycical bottom edge, not leaving enough room to draw at least three items if there’s more than three to begin with.
GetParentMenuInfo is used to quantify the index for both RgnHandles, cumParentRgn & menuParentRgn, when dealing with hierarchical Menus.
See the intro comments for RedrawParentItem for its purpose in life.
wMenuSelect is no longer the nightmare it initially was, thanks to Ben Cranston. Initially I had locked the passed wMenuBarHandle & surrounded darn near the entire PROC with a WITH theMenuBar^^. But then, with recursion for a hierarchical Menu, I called wInsertMenu which immediately unlocked wMenuBarHandle & called MoveHHi. Oh where, o’ where did the handle go ... thanks Ben !!!!!
{24} FUNCTION wMenuSelect (theMenuBar: wMenuBarHandle; startPt: Point): LONGINT; { Pull down the Menus and let the user select an Item: } { NOTE -- “startPt” is in Global coordinates, but } { ONLY to be compatible with _MenuSelect. } LABEL 100, 200; CONST TopMenuItemLoc = $A0A; AtMenuBottomLoc = $A0C; MenuFlashAddr = $A24; HiliteMode = $938; VAR oldPort: GrafPtr; { Many Locals because of recursion } oldClip: RgnHandle; oldForeColor, oldBackColor: RGBColor; screenBounds, betweenMSAs, visAllTitles, visThisTitle, visString: Rect; menuRect, menuFrameRect, lsMenuRect, lsWBMrect, lsTitleRect: Rect; lStartPt, hierPt, NILPt: Point; itemsHeight, i, j, blink: INTEGER; saveScrollInfo: LONGINT; menuFlashP: wordPtr; strayed: BOOLEAN; cmdChar, itemMark: char; hierMenuHdl: MenuHandle; origMCTable, parentMCTable, hierMCTable: MCTableHandle; hierSelect: LONGINT; onCScreen: CGrafPort; onCScreenPtr: CGrafPtr; onBWScreen: GrafPort; onBWScreenPtr: GrafPtr; MenuDownOSHdl: OffScreenRecHdl; { Murphy’s Memory Manager ... } tempRgn1, tempRgn2, tempRgn3: RgnHandle; FUNCTION ItDoesFit (theMenu: MenuHandle; maxMenuHeight: INTEGER; VAR cumMenuHeight: INTEGER): BOOLEAN; { Get maximum cum height that can fit } { within the specified max height. } VAR prevMenuHeight, index, sizeItemIcon: INTEGER; BEGIN ItDoesFit := true; { Be upbeat !! } IF theMenu^^.menuHeight <= maxMenuHeight THEN BEGIN cumMenuHeight := theMenu^^.menuHeight; EXIT(ItDoesFit); END; cumMenuHeight := 0; { For starters ... } FOR index := 1 TO CountMItems(theMenu) DO BEGIN prevMenuHeight := cumMenuHeight; sizeItemIcon := GetItemIconSize(theMenu, index); cumMenuHeight := cumMenuHeight + aboveBelowItem + Max(sizeFont, sizeItemIcon) + aboveBelowItem; IF cumMenuHeight > maxMenuHeight THEN Leave; { FOR loop } END; { FOR } IF index - 1 < 3 THEN ItDoesFit := false ELSE IF sizeItemIcon = 0 THEN { ... of first item NOT shown. } cumMenuHeight := prevMenuHeight ELSE { Prevents drawing of the top pixels of } { any icon in first item NOT shown: } cumMenuHeight := prevMenuHeight - aboveBelowItem; END; { ItDoesFit } FUNCTION GetParentMenuInfo (theMenuBar: wMenuBarHandle; hierMenuNbr: INTEGER): LONGINT; { Returns parent menu # in Hi word and } { parent item # in the low word: } { NEVER say it can NEVER fail !!! } VAR hierID, parentMenuNum, j: INTEGER; parentMenuHdl: MenuHandle; cmdChar, parentMark: char; temp: LONGINT; BEGIN WITH theMenuBar^^ DO BEGIN parentMenuNum := 0; WHILE (parentMenuNum < numMenus) & (wMenus[hierMenuNbr].parentID <> wMenus[parentMenuNum].mh^^.menuID) DO parentMenuNum := parentMenuNum + 1; IF parentMenuNum = numMenus THEN { It better NOT be !!! } ; hierID := wMenus[hierMenuNbr].mh^^.menuID; parentMenuHdl := wMenus[parentMenuNum].mh; END; { WITH } FOR j := 1 TO CountMItems(parentMenuHdl) DO BEGIN GetItemCmd(parentMenuHdl, j, cmdChar); IF cmdChar = char(hMenuCmd) THEN BEGIN GetItemMark(parentMenuHdl, j, parentMark); IF ord(parentMark) = hierID THEN Leave; { FOR loop } END; END; { FOR } temp := parentMenuNum; temp := BitShift(temp, 16) + j; GetParentMenuInfo := temp; END; { GetParentMenuInfo } PROCEDURE RedrawParentItem (theMenuBar: wMenuBarHandle; hierMenuNbr: INTEGER); { Apple’s MenuDefProc does NOT call _InverRect for a } { color Mac, but rather _EraseRect followed by a } { redraw of the Menu item with reversed background } { and hilite colors. As you can see below, however, } { I am prone to cheating: } { CONST } { TopMenuItemLoc = $A0A; } { AtMenuBottomLoc = $A0C; } VAR saveScrollInfo: LONGINT; { MUST be local !!! } parentMenuInfo: LONGINT; parentMenuNum, redrawitem: INTEGER; redrawPt: Point; parentMenuHdl: MenuHandle; parentMenuRect: Rect; BEGIN IF NOT aMac2 THEN EXIT(RedrawParentItem); parentMenuInfo := GetParentMenuInfo(theMenuBar, hierMenuNbr); parentMenuNum := HiWord(parentMenuInfo); redrawItem := LoWord(parentMenuInfo); WITH theMenuBar^^.wMenus[parentMenuNum] DO BEGIN parentMenuHdl := mh; parentMenuRect := menuParentRgn^^.rgnBBox; END; ClipRect(parentMenuRect); WITH lScreen.bounds DO { LocalToGlobal } OffsetRect(parentMenuRect, -left, -top); SetPt(redrawPt, 0, 0); saveScrollInfo := longPtr(TopMenuItemLoc)^; wordPtr(TopMenuItemLoc)^ := parentMenuRect.top; wordPtr(AtMenuBottomLoc)^ := parentMenuRect.bottom; MenuDefGlue(mChooseMsg, parentMenuHdl, parentMenuRect, redrawPt, redrawItem); longPtr(TopMenuItemLoc)^ := saveScrollInfo; END; { RedrawParentItem } BEGIN { wMenuSelect } { Note that starting here, we sure could use } { WITH theMenuBar^^ & WITH wMenus[i] statements } { for shorthand. However, we’d then need to call } { HLock. This would be okay except when we later } { implement Hierarchical menus via a recursive to } { ourself (wMenuSelect). This unnecessarily } { keeps a locked Handle around for a long time. } { [ See “The Secret Life of the Memory Manager” } { by Richard Clark n “develop”, Volume 1, } { Issue 2, April 1990 ] } { In addition, for Hierarchical menus, we call } { wInsertMenu and wDeleteMenu which in turn call } { the IncreaseSize and DecreaseSize PROCs. These } { latter PROCs move “theMenuBar” because we need } { to temporarily unlock this handle therefore } GetPort(oldPort); SetPort(theMenuBar^^.wp); oldClip := NewRgn; GetClip(oldClip); ; IF aMac2 THEN BEGIN { “GetColors” is local due to recursion. } GetBackColor(oldBackColor); GetForeColor(oldForeColor); origMCTable := GetMCInfo; SetMCInfo(theMenuBar^^.wMCTable); END; { IF aMac2 } ; menuFlashP := wordPtr(MenuFlashAddr); WHILE WaitMouseUp DO BEGIN whichItem := 0; { May NOT be in a titleRect. } { Find the Menu title that user is selecting. } i := theMenuBar^^.numMenus - 1; GlobalToLocal(startPt); { Local window coordinates. } WHILE (i >= 0) & NOT PtInRect(startPt, theMenuBar^^.wMenus[i].titleRect) DO i := i - 1; IF i >= 0 THEN { Have Menu drop ... } BEGIN lsTitleRect := theMenuBar^^.wMenus[i].titleRect; ; IF theMenuBar^^.wMenus[i].menuType = regMenu THEN BEGIN { Otherwise strayed = TRUE momentarily as you hit the } { line bordering titleRect & menuRect in the process } { of pulling down a MENU (see below): } lsTitleRect.bottom := lsTitleRect.bottom + menuFrame; { ... AND if the Window Menu Bar is scrolled ... } { don’t forget about the invertOverlap arithmetic } { which would enable the Mouse to be over the expanded } { titleRect WITHOUT the first/last title } { character being visible. The equations below avoid } { this happenstance. In addition, “menuFrame” is used } { to account for a character width > an image width as } { well as a character origin > 0. } screenBounds := gScreen; { = GrayRgn’s rgnBBox. } GlobalLocal(screenBounds); betweenMSAs := WBMrect; InsetRect(betweenMSAs, leftMSArect.right, 0); IF NOT SectRect(screenBounds, betweenMSAs, visAllTitles) THEN GOTO 100; { Will NOT happen !! } IF NOT SectRect(visAllTitles, lsTitleRect, visThisTitle) THEN GOTO 100; { Ditto !! } visString := visThisTitle; WITH visString DO BEGIN IF (leftMSArect.right > right - invertOverlap - 2 * menuFrame) | (screenBounds.left > right - invertOverlap - 2 * menuFrame) THEN right := right - invertOverlap - 2 * menuFrame; ; IF (rightMSArect.left < left + invertOverlap + 2 * menuFrame) | (screenBounds.right < left + invertOverlap + 2 * menuFrame) THEN left := left + invertOverlap + 2 * menuFrame; END; { WITH visString } IF EmptyRect(visString) THEN GOTO 100; END; { a regular Menu } LocalGlobal(lsTitleRect); { Needed for vertical scrolling: } lsWBMrect := WBMrect; { ... or rightMSArect.left: } InsetRect(lsWBMrect, leftMSArect.right, 0); LocalGlobal(lsWBMrect); LocalToGlobal(startPt); CalcMenuSize(theMenuBar^^.wMenus[i].mh); WITH theMenuBar^^.wMenus[i], titleRect DO IF menuType = regMenu THEN SetRect(menuFrameRect, left, bottom, left + menuFrame + mh^^.menuWidth + frameShad, bottom + menuFrame + mh^^.menuHeight + frameShad) ELSE SetRect(menuFrameRect, right - 4 * menuFrame, top, right - 4 * menuFrame + menuFrame + mh^^.menuWidth + frameShad, top + menuFrame + mh^^.menuHeight + frameShad); { If Menu overlaps the screen’s edges, } { trim and/or shift it: } LocalGlobal(menuFrameRect); WITH gScreen DO BEGIN IF menuFrameRect.bottom > bottom - 2 * menuFrame THEN menuFrameRect.bottom := bottom - 2 * menuFrame; ; IF menuFrameRect.right > right - 2 * menuFrame THEN { Shift left. } IF theMenuBar^^.wMenus[i].menuType = regMenu THEN OffsetRect(menuFrameRect, right - 2 * menuFrame - menuFrameRect.right, 0) ELSE OffsetRect(menuFrameRect, -(theMenuBar^^.wMenus[i].titleRect.right - theMenuBar^^.wMenus[i].titleRect.left - 8 * menuFrame + (menuFrameRect.right - menuFrameRect.left)), 0); ; IF menuFrameRect.left < left + 2 * menuFrame THEN { Shift right. } OffsetRect(menuFrameRect, left + 2 * menuFrame - menuFrameRect.left, 0); END; { WITH gScreen } GlobalLocal(menuFrameRect); { --> local window coords. } WITH menuFrameRect DO IF ItDoesFit(theMenuBar^^.wMenus[i].mh, bottom - top - menuFrame - frameShad, itemsHeight) THEN bottom := top + menuFrame + itemsHeight + frameShad ELSE IF theMenuBar^^.wMenus[i].menuType = regMenu THEN BEGIN { Damn SysBeep drove me nuts/nuttier !!! } wHiliteMenu(theMenuBar, theMenuBar^^.wMenus[i].mh^^.menuID); Delay(flashDelay * 2, finalTicks); wHiliteMenu(theMenuBar, 0); Delay(flashDelay * 2, finalTicks); GOTO 100; END ELSE BEGIN { From Open(C)Port on the previous go-around. } SetPort(oldPort); WITH lScreen.bounds DO OffsetRect(lsTitleRect, left, top); { GlobalToLocal } Delay(flashDelay * 2, finalTicks); IF aMac2 THEN { i = Hierarchical Menu # } RedrawParentItem(theMenuBar, i) ELSE BEGIN ClipRect(lsTitleRect); InvertRect(lsTitleRect); END; Delay(flashDelay * 2, finalTicks); { So we can reset its clipRgn on exit: } SetPort(theMenuBar^^.wp); Leave; END; { does NOT fit & WITH menuFrameRect } { MenuDefProc’s “mChooseMsg” handles hiliting } { for a Hierarchical menu item: } IF theMenuBar^^.wMenus[i].menuType = regMenu THEN wHiliteMenu(theMenuBar, theMenuBar^^.wMenus[i].mh^^.menuID); MenuDownOSHdl := CreateOffScreen(menuFrameRect); IF (QuickStrip(Ptr(MenuDownOSHdl)) = NIL) | (MenuDownOSHdl^^.CreateOffScreenError <> noErr) THEN BEGIN DisposOffScreen(MenuDownOSHdl); GOTO 200; END; { Whoops !! } ClipRect(menuFrameRect); { Draw off-screen ... } EraseRect(menuFrameRect); { Eliminate all stray matter. } { An alternate “ToOnScreen” so our Menu can overlap } { the window’s boundaries rather than be confined } { to just its portRect: } IF aMac2 THEN BEGIN SetGDevice(MenuDownOSHdl^^.oldDevice); onCScreenPtr := @onCScreen; OpenCPort(onCScreenPtr); { For multiple screens ... } RectRgn(onCScreenPtr^.visRgn, gScreen); onCScreenPtr^.portRect := gScreen; END { IF aMac2 } ELSE BEGIN onBWScreenPtr := @onBWScreen; OpenPort(onBWScreenPtr); RectRgn(onBWScreenPtr^.visRgn, gScreen); onBWScreenPtr^.portRect := gScreen; END; { ELSE = “Yucky” black-and-white } ClipRect(menuFrameRect); { Save current screen image: } BackColor(whiteColor); ForeColor(blackColor); CopyBits(thePort^.portBits, MenuDownOSHdl^^.offBitMapPtr^, menuFrameRect, menuFrameRect, srcCopy, NIL); TextSize(sizeFont); TextFont(systemFont); TextFace([]); TextMode(srcOr); { NOW, the magic ~~ Mike Shuster, MacTutor <Dec, 85> } menuRect := menuFrameRect; WITH menuRect DO BEGIN { Draw frame, then shadow. } GetPenState(pnState); PenNormal; right := right - menuShadow; bottom := bottom - menuShadow; wSetColorMenu(theMenuBar^^.wMenus[i].mh^^.menuID, mceMenuTitle); EraseRect(menuRect); { Inside shadow. } PenSize(menuFrame, menuFrame); FrameRect(menuRect); { Inside frame AND shadow: } InsetRect(menuRect, menuFrame, menuFrame); ; MoveTo(left + menuShadow, bottom + menuFrame); PenSize(menuShadow, menuShadow); Line((right - left + menuFrame), 0); MoveTo(right + menuFrame, bottom + frameShad); Line(0, -(bottom - top + menuFrame)); SetPenState(pnState); END; { WITH menuRect } lsMenuRect := menuRect; { Save for later ... } WITH lScreen DO BEGIN OffsetRect(menuRect, -left, -top); { LocalToGlobal } OffsetRect(lsTitleRect, left, top); { GlobalToLocal } OffsetRect(lsWBMrect, left, top); END; { WITH lScreen } { ... or parentID = mainMenu. } IF theMenuBar^^.wMenus[i].menuType = regMenu THEN BEGIN tempRgn1 := NewRgn; RectRgn(tempRgn1, lsWBMrect); theMenuBar^^.wMenus[i].cumParentRgn := tempRgn1; tempRgn2 := NewRgn; RectRgn(tempRgn2, lsMenuRect); theMenuBar^^.wMenus[i].menuParentRgn := tempRgn2; END ELSE BEGIN { i = Hierarchical Menu # } j := HiWord(GetParentMenuInfo(theMenuBar, i)); tempRgn1 := NewRgn; tempRgn2 := theMenuBar^^.wMenus[j].cumParentRgn; tempRgn3 := theMenuBar^^.wMenus[j].menuParentRgn; UnionRgn(tempRgn2, tempRgn3, tempRgn1); theMenuBar^^.wMenus[i].cumParentRgn := tempRgn1; tempRgn2 := NewRgn; RectRgn(tempRgn2, lsMenuRect); theMenuBar^^.wMenus[i].menuParentRgn := tempRgn2; END; { Start fresh with each scrollable Menu: } saveScrollInfo := longPtr(TopMenuItemLoc)^; wordPtr(TopMenuItemLoc)^ := menuRect.top; wordPtr(AtMenuBottomLoc)^ := menuRect.bottom; { I encountered a few “cosmetic” problems with Apple’s } { MenuDefProc. } { 1) the disabled dotted line will overwrite the right } { border of the frame. } { 2) if there is an icon immediately below the last } { item that shows in a scrolling menu, the bottom } { border of the frame is overwritten by a portion } { of said icon. } { The call to ClipRect solves both: } ClipRect(lsMenuRect); { whichItem := 0; -- at beginning of WHILE WaitMouseUp DO } MenuDefGlue(mDrawMsg, theMenuBar^^.wMenus[i].mh, menuRect, startPt, whichItem); strayed := false; { = Mouse on Menu Item OR on Menu } { title OR NOT on another Menu. } WHILE WaitMouseup & NOT strayed DO { While still selecting in this Menu ... } BEGIN GetMouse(startPt); lStartPt := startPt; { LocalToGlobal } SubPt(lScreen.topLeft, startPt); MenuDefGlue(mChooseMsg, theMenuBar^^.wMenus[i].mh, menuRect, startPt, whichItem); IF whichItem <> 0 THEN { Item is enabled. } BEGIN GetItemCmd(theMenuBar^^.wMenus[i].mh, whichItem, cmdChar); IF cmdChar = char(hMenuCmd) THEN { a sub Menu ... } BEGIN IF aMac2 THEN parentMCTable := theMenuBar^^.wMCTable; hierPt := startPt; GetItemMark(theMenuBar^^.wMenus[i].mh, whichItem, itemMark); hierMenuHdl := GetMenu(ord(itemMark)); wInsertMenu(theMenuBar, hierMenuHdl, hierMenu); DetachResource(Handle(hierMenuHdl)); IF aMac2 THEN BEGIN hierMCTable := GetMCInfo; SetMCInfo(parentMCTable); theMenuBar^^.wMCTable := hierMCTable; END; { IF aMac2 } hierSelect := wMenuSelect(theMenuBar, hierPt); wDeleteMenu(theMenuBar, ord(itemMark)); ; whichMenu := HiWord(hierSelect); whichItem := LoWord(hierSelect); IF whichItem <> 0 THEN { So a parent Menu Item does NOT blink: } BEGIN strayed := true; Leave; END; END ELSE { a regular Menu } whichMenu := theMenuBar^^.wMenus[i].mh^^.menuID; END; { enabled Item } strayed := NOT PtInRgn(lStartPt, theMenuBar^^.wMenus[i].menuParentRgn) & NOT PtInRect(lStartPt, lsTitleRect) & PtInRgn(lStartPt, theMenuBar^^.wMenus[i].cumParentRgn); END; { WHILE WaitMouseup & NOT strayed } IF (whichItem <> 0) & NOT strayed THEN FOR blink := 1 TO menuFlashP^ DO BEGIN SetPt(NILPt, 0, 0); MenuDefGlue(mChooseMsg, theMenuBar^^.wMenus[i].mh, menuRect, NILPt, whichItem); Delay(flashDelay DIV 4, finalTicks); MenuDefGlue(mChooseMsg, theMenuBar^^.wMenus[i].mh, menuRect, startPt, whichItem); Delay(flashDelay DIV 4, finalTicks); END; { blinking the selected item } ClipRect(menuFrameRect); BackColor(whiteColor); ForeColor(blackColor); { Blit back original screen image. For a Hierarchical menu } { item, the off-screen bitMap/pixMap will include a part of } { the inverted parent item. Therefore, we must first blit } { back and THEN re-invert !!! } CopyBits(MenuDownOSHdl^^.offBitMapPtr^, thePort^.portBits, menuFrameRect, menuFrameRect, srcCopy, NIL); { Un-hilite the title: } IF theMenuBar^^.wMenus[i].menuType = regMenu THEN wHiliteMenu(theMenuBar, 0) ELSE IF aMac2 THEN { i = Hierarchical Menu # } RedrawParentItem(theMenuBar, i) ELSE BEGIN ClipRect(lsTitleRect); InvertRect(lsTitleRect); END; IF aMac2 THEN CloseCPort(onCScreenPtr) ELSE ClosePort(onBWScreenPtr); SetPort(theMenuBar^^.wp); { Original menuFrameRect in window coordinates: } ValidRect(MenuDownOSHdl^^.drawingRect); DisposOffScreen(MenuDownOSHdl); DisposeRgn(theMenuBar^^.wMenus[i].menuParentRgn); DisposeRgn(theMenuBar^^.wMenus[i].cumParentRgn); longPtr(TopMenuItemLoc)^ := saveScrollInfo; IF theMenuBar^^.wMenus[i].menuType = hierMenu THEN Leave; END { IF i >= 0 } ELSE { User is not over a Menu. } ; 100: GetMouse(startPt); LocalToGlobal(startPt); END; { WHILE WaitMouseUp } IF whichItem = 0 THEN wMenuSelect := 0 ELSE wMenuSelect := BitShift(whichMenu, 16) + whichItem; 200: IF aMac2 THEN BEGIN RGBBackColor(oldBackColor); { “SetColors” is local } RGBForeColor(oldForeColor); { due to recursion. } SetMCInfo(origMCTable); END; { aMac2 } ; SetClip(oldClip); DisposeRgn(oldClip); SetPort(oldPort); END; { wMenuSelect }
There’s nothing super different in wMenuKey from Jim Matthews’ code except I utilized GetItemCmd to shorten it a tad.
{25} FUNCTION wMenuKey (theMenuBar: wMenuBarHandle; ch: char): LONGINT; VAR i, j, whichMenu, whichItem: INTEGER; found, enabled: BOOLEAN; cmdChar: char; FUNCTION equalChars (c1, c2: char): BOOLEAN; { Filter out difference between UPPER and lower case: } BEGIN IF c1 IN [‘a’..’z’] THEN c1 := char(ord(c1) + ord(‘A’) - ord(‘a’)); IF c2 IN [‘a’..’z’] THEN c2 := char(ord(c2) + ord(‘A’) - ord(‘a’)); equalChars := (c1 = c2); END; { equalChars } BEGIN { wMenuKey } mbHState := HGetState(Handle(theMenuBar)); MoveHHi(Handle(theMenuBar)); HLock(Handle(theMenuBar)); found := false; i := 0; WITH theMenuBar^^ DO BEGIN WHILE (NOT found) & (i < numMenus) DO BEGIN WITH wMenus[i] DO FOR j := 1 TO CountMItems(mh) DO BEGIN GetItemCmd(mh, j, cmdChar); IF equalChars(cmdChar, ch) THEN BEGIN found := true; whichMenu := mh^^.menuID; whichItem := j; Leave; { FOR loop } END; { IF equalChars } END; { FOR scanning each individual Menu & } { WITH wMenus[i] } i := i + 1; { IF found, i = correct # plus 1. } END; { WHILE scanning each whole Menu } IF found THEN BEGIN { The Item is enabled if both it AND } { its Menu title are enabled: } IF j < 32 THEN enabled := BitTst(@wMenus[i - 1].mh^^.enableFlags, menuTitleBit - j) ELSE enabled := true; { Items 32 & beyond. } enabled := enabled & BitTst(@wMenus[i - 1].mh^^.enableFlags, menuTitleBit); END; { IF found } END; { WITH theMenuBar^^ } HSetState(Handle(theMenuBar), mbHState); IF found & enabled THEN BEGIN wHiliteMenu(theMenuBar, whichMenu); Delay(flashDelay, finalTicks); wHiliteMenu(theMenuBar, 0); wMenuKey := BitShift(whichMenu, 16) + whichItem; END { IF found & enabled } ELSE wMenuKey := 0; END; { wMenuKey }
The prolific comments tell the whole gory story ...
{26} PROCEDURE wHiliteMenu (theMenuBar: wMenuBarHandle; menuID: INTEGER); { NOTE that this PROC is used only for a regular Menu item. } { Apple’s MenuDefProc handles a hierarchical Menu item. } { For a regular Menu item, we invert the portion of the } { titleRect that is both totally visible on the screen and } { between the two MSAs. } CONST { What happened to a simple InvertRect ?!!? } HiliteMode = $938; VAR origPort: GrafPtr; origClip: RgnHandle; i: INTEGER; screenBounds, betweenMSAs, visAllTitles, visThisTitle, visString: Rect; { Gotta be local to avoid interference } { with the global = mbHState: } lmbHState: SignedByte; oldMCTable: MCTableHandle; { Color stuff ... } oldForeColor, oldBackColor, hiliteForeColor, hiliteBackColor: RGBColor; oldTxSize, oldTxFont, oldTxMode: INTEGER; oldTxStyle: Style; BEGIN { Needed because wHiliteMenu is called with pressing } { a CMD-key whereupon a window may not be around. } IF FrontWindow = NIL THEN EXIT(wHiliteMenu); lmbHState := HGetState(Handle(theMenuBar)); IF NOT BitTst(@lmbHState, 0) THEN BEGIN MoveHHi(Handle(theMenuBar)); HLock(Handle(theMenuBar)); END; { NOT already locked !! } WITH theMenuBar^^ DO BEGIN GetPort(origPort); SetPort(wp); origClip := NewRgn; GetClip(origClip); ; { Just as with wMenuSelect PROC because wMenuKey calls us. } oldTxSize := thePort^.txSize; oldTxFont := thePort^.txFont; oldTxStyle := thePort^.txFace; oldTxMode := thePort^.txMode; TextSize(sizeFont); TextFont(systemFont); TextFace([]); TextMode(srcOr); ; IF aMac2 THEN BEGIN GetForeColor(oldForeColor); GetBackColor(oldBackColor); oldMCTable := GetMCInfo; SetMCInfo(theMenuBar^^.wMCTable); END; screenBounds := gScreen; GlobalLocal(screenBounds); betweenMSAs := WBMrect; { ... or rightMSArect.left: } InsetRect(betweenMSAs, leftMSArect.right, 0); IF SectRect(screenBounds, betweenMSAs, visAllTitles) THEN BEGIN { Un-highlight previously highlighted Menu title by } { re-inverting it or re-drawing it if on a machine } { with Color QuickDraw. This is similiar to the } { gyrations within Apple’s MenuDefProc. The latter } { does NOT call _InverRect for a color Mac, but } { rather _EraseRect followed by a redraw of the } { Menu item. } IF titleHilited <> noneHilited THEN BEGIN WITH wMenus[titleHilited], titleRect DO BEGIN IF SectRect(visAllTitles, titleRect, visThisTitle) THEN BEGIN { ALWAYS true if hilited. } ClipRect(visThisTitle); IF aMac2 THEN { See wDrawMenuBar PROC. } BEGIN wSetColorMenu(mceMenuBar, mceMenuBar); EraseRect(visThisTitle); MoveTo(left + invertOverlap, bottom - (mBarHt - menuFrame - sizeFont) DIV 2); wSetColorMenu(mh^^.menuID, mceMenuTitle); DrawString(mh^^.menuData); END ELSE InvertRect(visThisTitle); ValidRect(visThisTitle); END; { IF SectRect: visThisTitle NOT empty } END; { WITH } ; titleHilited := noneHilited; { So we don’t re-invert. } END; { IF hilited } IF menuID <> 0 THEN BEGIN i := 0; WHILE (i < numMenus) & (wMenus[i].mh^^.menuID <> menuID) DO i := i + 1; IF i <> numMenus THEN WITH wMenus[i] DO BEGIN IF SectRect(visAllTitles, titleRect, visThisTitle) THEN BEGIN visString := visThisTitle; WITH visString DO BEGIN IF (leftMSArect.right > right - invertOverlap - 2 * menuFrame) | (screenBounds.left > right - invertOverlap - 2 * menuFrame) THEN right := right - invertOverlap - 2 * menuFrame; ; IF (rightMSArect.left < left + invertOverlap + 2 * menuFrame) | (screenBounds.right < left + invertOverlap + 2 * menuFrame) THEN left := left + invertOverlap + 2 * menuFrame; END; { WITH visString } IF NOT EmptyRect(visString) THEN BEGIN ClipRect(visThisTitle); IF aMac2 THEN BEGIN { Text’s color becomes the background color and the } { MenuBar’s background color becomes the foreground. } wSetColorMenu(menuID, mceMenuTitle); GetForeColor(hiliteBackColor); HiliteColor(hiliteBackColor); wSetColorMenu(mceMenuBar, mceMenuBar); { GetBackColor(hiliteForeColor); } { RGBBackColor(hiliteForeColor); } BitClr(Ptr(HiliteMode), pHiliteBit); END; { on a color machine } InvertRect(visThisTitle); ValidRect(visThisTitle); ; titleHilited := i; END; { visString NOT empty } END; { IF SectRect: visThisTitle NOT empty } END; { IF i <> numMenus & WITH } END { IF menuID <> 0 } ELSE { as in HiliteMenu(0). } ; { Taken care of by IF titleHilited <> noneHilited } END; { IF SectRect: visAllTitles NOT empty } IF aMac2 THEN BEGIN SetMCInfo(oldMCTable); RGBForeColor(oldForeColor); RGBBackColor(oldBackColor); END; ; TextSize(oldTxSize); TextFont(oldTxFont); TextFace(oldTxStyle); TextMode(oldTxMode); ; SetClip(origClip); DisposeRgn(origClip); SetPort(origPort); END; { WITH theMenuBar^^ } HSetState(Handle(theMenuBar), lmbHState); END; { wHiliteMenu }
Called in response to zooming & growing the window.
Note: NOT zooming = growing.
{27} PROCEDURE wChangeMenuBarSize (wp: WindowPtr; zooming: BOOLEAN); BEGIN mBar := wGetMenuBar(wp); ; IF mBar <> NIL THEN BEGIN { Only a part of wClearMenuBar ... } GetWBMrects(wp, WBMrect, leftMSArect, rightMSArect); wHiliteMenu(mBar, 0); IF NOT zooming THEN { doZoom calls InvalRect(wp^.portRect) } InvalRect(rightMSArect); IF mBar^^.rightScrollPoly <> NIL THEN BEGIN KillPoly(mBar^^.rightScrollPoly); mBar^^.rightScrollPoly := NIL; { Mark as gone !! } END; IF zooming & (mBar^^.leftScrollPoly <> NIL) THEN BEGIN KillPoly(mBar^^.leftScrollPoly); mBar^^.leftScrollPoly := NIL; END; END; { window has a WBM } END; { wChangeMenuBarSize } END. { UNIT = wBarMenuProc }
Thank goodness it’s over ...
I lie ... even though I have NOT presented the source in “wBMBalloons.p” here, the application incorporates Balloon Help as in big System 7.0 ...and here’s a sneak preview ...
{28} UNIT wBMBalloons; INTERFACE USES Types, Quickdraw, Menus, TextEdit, Traps, GestaltEqu, Balloons, wBMGlobals, wBMMiscSubs, wBMScrollSubs; FUNCTION HelpManagerActive: BOOLEAN; FUNCTION BalloonsOn: BOOLEAN; FUNCTION BalloonShowing: BOOLEAN; PROCEDURE FindAndShowDynamicBalloons (balloonsUp: BOOLEAN; window: WindowPtr); PROCEDURE HideBalloons (balloonsUp: BOOLEAN); PROCEDURE ShowBalloons (balloonsUp: BOOLEAN); PROCEDURE ResetBalloons (balloonsUp: BOOLEAN);
NOW it’s over, really and truly ..
Well ... almost!!! What about AppleEvents? ... it’s on disk!!
{29} UNIT myAppleEvents; INTERFACE USES Types, Memory, OSUtils, Quickdraw, Events, Files, AppleTalk, PPCToolbox, Processes, EPPC, Notification, AppleEvents, Script, Packages, Dialogs, CTBUtilities, Connections, GestaltEqu, wBMGlobals, wBMMiscSubs; FUNCTION AppleEventsActive: BOOLEAN; FUNCTION PPCToolboxActive: BOOLEAN; FUNCTION DoAEOpenApplication (message: AppleEvent; reply: AppleEvent; refcon: LONGINT): OSErr; FUNCTION DoAEOpenDocuments (message: AppleEvent; reply: AppleEvent; refcon: LONGINT): OSerr; FUNCTION DoAEPrintDocuments (message: AppleEvent; reply: AppleEvent; refcon: LONGINT): OSErr; FUNCTION DoAEQuitApplication (message: AppleEvent; reply: AppleEvent; refcon: LONGINT): OSErr; FUNCTION InitAppleEvents: OSErr; FUNCTION MissedAnyParameters (VAR event: EventRecord; message: AppleEvent): BOOLEAN; PROCEDURE DoHighLevelEvent (event: EventRecord);
P.P.S The SARez resource code is on dis k too
SEE!!!
the little guy here this must prove I’m done -->

- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine