Hiding the Menu Bar
Bill Catambay, Pascal Developer



Updated 1-4-00

One of the most frequently asked questions I've seen both in the Pascal group, and in Macintosh programming newsgroups in general, is how to hide the menu bar. There have been many different renditions of code posted to show how to do this, so I thought it was time to post that code right here at Pascal Central (the Pascal version of the code, of course).

The code here does more than just visually hide the menu bar. Even after you hide the menu bar, the Mac OS still recognizes that area of the screen as reserved for the menu bar. If you were to display a window covering the entire screen, you would not have access to the menu bar region without doing something extra to give you access to it.

Basically, the steps for hiding the menu bar (and relinquishing control of that region to your program) are as follows:

  1. Save the height of the menu bar
  2. Create a new region for the menu bar
  3. Set the height of the menu bar to zero (this visually hides the menu bar)
  4. Get the "Gray" region (region your program has access to)
  5. Set the new Gray region to the union of the menu bar region and the Gray region (this gives your program access to the menu bar region)

Conversely, the steps for restoring the menu bar (which would be a nice thing to do before exiting the program) are:

  1. Set the menu bar height back to the original saved height
  2. Remove the menu bar region from the Gray region
  3. Draw the menu bar

To fully demonstrate hiding the menu bar, this sample program also animates an oscillating ball (graphed to a sine wave) along the top part of the screen, including writing into the menu bar area. The trickiest part of this sample code ended up being something that has nothing to do with what I was trying to demonstrate. Once the ball hits the end of the screen (far right side), it starts again from the left. I wanted the ball to do a smooth wrap, including being partially on the right end of the screen, while wrapped to the beginning of the left side of the screen. This kind of code would be useful in game programming, such as Asteroids.

The source code is designed for both CodeWarrior Pascal (68K or PPC) and Think Pascal. It provides some, but not complete, error checking (e.g., if you run with the default memory allocation on a 17 inch screen in thousands of colors, the program dies abruptly due to not enough memory allocated). The latest testing of the code was performed with CW 10.

Download the Pascal project
(source code, resource file and CW10 project file)

As an added bonus, Ranko Bojanic has converted the HideMenuBar project into Assembly Language. With the recent announcement of Metrowerks dropping Pascal, Ranko began experimenting with Fantasm PPC Assembly. Ranko raves about the product, "After studying it for a month, I was able to start writing Macintosh applications using Fantasm's PPC assembly language. PPC assembly language (RISC) is much simpler than 68K assembly language." About the company itself, Ranko affirms, "I like Lightsoft because Macintosh is the only platform they support." If you're interested in seeing the Assembly version of HideMenuBar, download the package below. To find out more about Fantasm PPC Assembly, check out Lightsoft's web site at http://www.lightsoft.co.uk.

Download the Assembly project
The PPC Assembly project contains source code, resource file, Fantasm project file
and the compiled application. The source code (in the src folder) can be read with
any text editor. To run the Fantasm project file, go to the Lightsoft's web site
mentioned above and download a demo version of Fantasm.


{***********************************************************}
{  This is a quick program to demonstrate hiding the menu   }
{  bar in Pascal.                                           }
{                                                           }
{  Written by Bill Catambay, 8/24/95.                       }
{                                                           }
{  This program hides the menu bar and creates a window     }
{  which occupies the menu bar space (as well as the rest   }
{  of the screen). It then animates a ball across the       }
{  screen to demonstrate writing over the menu bar.         }
{                                                           }
{  It uses the sine wave function to animate the ball,      }
{  and performs a "wrap" when the ball hits the end (and    }
{  slows the ball down and plays a sound for effect.  It    }
{  also continues the ball in the correct location from     }
{  the wrap rather than starting it in the same place.      }
{                                                           }
{  9/8/95:  Fixed the ball to no longer flicker using       }
{  offscreen worlds.  Added compiler directives to get      }
{  program to work in both CodeWarrior and Think Pascal.    }
{                                                           }
{ Send all comments to Bill Catambay                        }
{                                                           }
{***********************************************************}

{I've made a few fixes for Think-friendliness, but also cleaned up}
{the code a bit, reduced the number of $IFC's. The biggest problem}
{was that the Think project had only 128k of memory assigned in Run}
{options, and the GWorld needs a lot more. /Ingemar}

Program HideMenuBar;

{$IFC UNDEFINED THINK_PASCAL}
Uses
	Sound, Fonts, Windows, Dialogs, ToolUtils, Resources, LowMem, QDOffscreen,
	Processes, Fp;

{$ELSEC}
Uses
	Sound, QDOffscreen;

{Some type definitions to make new interfaces work with Think:}
Type
	WindowRef = WindowPeek;

	SndListHandle = Handle;
	NewSndCallBackProc = ProcPtr;
	SndCallBackUPP = ProcPtr;

Var
	qd: Record
		screenBits: BitMap;
		black: Pattern;
		End;
{$ENDC}

Const
	width = 20;
	height = 20;
	kResourceSoundComplete = 1;
	kHandleSoundComplete = 2;

Var
	save_mbar: integer;
	mBarRgn: rgnHandle;
	gBackWind: WindowPtr;
	gForeWind: WindowPtr;
	GrayRgn: RgnHandle;
	newPos, oldPos: point;
	newBox, oldBox: rect;
	mainRect: rect;
	err: OSErr;
	ticks: longint;
	savePat: GworldPtr;
	y, k, pi: real;
	SndChan: SndChannelPtr;
	InPlay: boolean;
	MySound: Handle;
	wrapping: boolean;

Procedure InitToolbox;

	Begin
{$IFC UNDEFINED THINK_PASCAL}
	InitGraf(@qd.thePort);
	InitFonts;
	InitWindows;
	InitMenus;
	TEinit;
	InitDialogs(Nil);
	MaxApplZone;
{$ELSEC}
	qd.screenBits := screenBits;
	qd.black := black;
{$ENDC}
	InitCursor;
	End;

{A few Low-mem interfaces that Think doesn't have:}
{$IFC UNDEFINED THINK_PASCAL}
{$ELSEC}
Function LMGetWindowList: WindowRef;
	Inline
		$2EB8, $09D6;			{ MOVE.l $09D6,(SP) }

Function GetMBarHeight: INTEGER;
	Inline
		$3EB8, $0BAA;			{ MOVE.w $0BAA,(SP) }

Function LMGetMBarHeight: Integer;
	Inline
		$3EB8, $0BAA;			{ MOVE.w $0BAA,(SP) }

Procedure LMSetMBarHeight (value: Integer);
	Inline
		$31DF, $0BAA;			{ MOVE.w (SP)+,$0BAA }

Function LMGetGrayRgn: RgnHandle;
	Inline
		$2EB8, $09EE;			{ MOVE.l $09EE,(SP) }

{$ENDC}

Procedure SH_ForceUpdate (rgn: RgnHandle);

Var
	wpFirst: WindowRef;

	Begin
	wpFirst := LMGetWindowList;
	PaintBehind(wpFirst, rgn);
	CalcVisBehind(wpFirst, rgn);
	End;

Procedure GetMBarRgn (mbarRgn: RgnHandle);

Var
	mbarRect: Rect;

	Begin
	mBarRect := qd.screenBits.bounds;
	mBarRect.bottom := mBarRect.top + save_mbar;
	RectRgn(mBarRgn, mBarRect);
	End;

Function CenterWind (srect, mainrect: rect): rect;

Var
	wrect: rect;

	Begin
	wrect.top := (mainrect.bottom - mainrect.top - (srect.bottom - srect.top)) Div 2 + mainrect.top;
	wrect.bottom := wrect.top + (srect.bottom - srect.top);
	wrect.left := (mainrect.right - mainrect.left - (srect.right - srect.left)) Div 2 + mainrect.left;
	wrect.right := wrect.left + (srect.right - srect.left);
	CenterWind := wrect;
	End; { of centerwind }

{$PUSH}
{$D-}
Procedure PlayAsyncCallback (chan: SndChannelPtr;
					    Var cmd: SndCommand);

	Begin
	If chan = Nil Then
		;
	InPlay := FALSE;
	End;
{$POP}

Procedure SoundDispose;

	Begin
	If SndChan <> Nil Then
		Begin
		Err := SndDisposeChannel(SndChan, True);
		SndChan := Nil;
		inPlay := false;
		End;
	End;

Procedure BackgroundSound;

Var
	mySndCmd: SndCommand;
	sndUPP: SndCallBackUPP;

	Begin
	sndUPP := NewSndCallBackProc(@PlayAsyncCallback);
	err := SndNewChannel(SndChan, sampledSynth, 0, sndUPP);
	If err <> NoErr Then
		exit(BackgroundSound);
	If SndChan = Nil Then
		exit(BackgroundSound);
	HLock(mysound);
	err := SndPlay(SndChan, SndListHandle(mySound), True);
	If err <> NoErr Then
		exit(BackgroundSound);
	inPlay := TRUE;
	With mySndCmd Do
		Begin
		cmd := callBackCmd;
		param1 := 0;
		param2 := 0;
		End;
	err := SndDoCommand(SndChan, mySndCmd, False);
	HUnlock(mysound);
	End;

Procedure RemoveMbar;

	Begin
	save_mbar := GetMBarHeight;
	mBarRgn := NewRgn;
	GetMBarRgn(mBarRgn);				{ make a region for the mbar }
	LMSetMBarHeight(0);
	GrayRgn := LMGetGrayRgn;
	UnionRgn(GrayRgn, mBarRgn, GrayRgn);
	SH_ForceUpdate(mBarRgn);
	End;

Procedure SetBackground;

Var
	r: rect;

	Begin
	gBackWind := NewCwindow(Nil, qd.screenBits.bounds, '', FALSE, 
		documentProc, Pointer(-1), TRUE, 0);
	UnionRect(GrayRgn^^.rgnBBox, qd.screenBits.bounds, r);
	MoveWindow(gBackWind, r.left, r.top, false);
	SizeWindow(gBackWind, r.right - r.left + width, r.bottom - r.top, true);
	BeginUpdate(gBackWind);
	ShowWindow(gBackWind);
	SetPort(gBackWind);
	{ we want to set the origin of the window to be the origin }
	{ of the global coordinate system so that the pattern we }
	{ draw is not offset from the desktop's pattern }
	SetOrigin(GrafPtr(gBackWind)^.portRect.left, 
		GrafPtr(gBackWind)^.portRect.top);
	r := gBackWind^.portRect;
	FillCRect(r, GetPixPat(16));	{ so use the 'ppat'=16 resource in system }
	SetOrigin(0, 0);
	EndUpdate(gBackWind);
	End;

Procedure SetForeground;

Var
	r: rect;

	Begin
	SetRect(r, 0, 0, 400, 100);
	r := CenterWind(r, qd.screenBits.bounds);
	gForeWind := NewCwindow(Nil, r, '', TRUE, plainDbox, Pointer(-1), TRUE, 0);
	SetPort(gForeWind);
	moveTo(20, 20);
	textsize(24);
	DrawString('Look Mom!  No menu bar!');
	moveTo(100, 50);
	textsize(10);
	DrawString('Pascal sample by Bill Catambay');
	moveTo(100, 65);
	DrawString('bill.m.catambay@nospam.com');
	moveTo(50, 90);
	textsize(12);
	DrawString('<press mouse button to quit>');
	End;

{$IFC UNDEFINED THINK_PASCAL}
{$ELSEC}
Function Acos (radians: real): real;

	Begin
	acos := 3.14159265359;  {  Defined only for radians = -1 }
	End;

Function Remainder (n, k: real): real;

	Var
		m: integer;

	Begin
	m := trunc(n / k);
	remainder := n - m * k;
	End;
{$ENDC}

Procedure InitGlobals;

Var
	r, s: rect;
	saveGD: GDHandle;
	saveGW: GWorldPtr;

	Begin
	SetPt(oldPos, 0, 50 + trunc(50 * sin(0 / 20)));
	SetRect(oldBox, oldPos.h, oldPos.v, oldPos.h + width, oldPos.v + height);
	SetRect(r, 0, 0, gBackWind^.portRect.right, 200);
	GetGWorld(saveGW, saveGD);
{$IFC UNDEFINED THINK_PASCAL}
	Err := NewGWorld(savePat, 0, r, Nil, Nil, 0);
{$ELSEC}
	Err := NewGWorld(savePat, 0, r, Nil, Nil, []);
{$ENDC}
	If Err <> noErr Then
		ExitToShell;
	If Not LockPixels(savepat^.portPixMap) Then
		ExitToShell;
	SetGWorld(savepat, Nil);
	SetRect(r, 0, 0, gBackWind^.portRect.right - width, 200);
	CopyBits(gBackWind^.portbits, GrafPtr(savePat)^.portBits, r, r, srcCopy, 
		Nil);
	SetRect(r, gBackWind^.portRect.right - width, 0, 
		gBackWind^.portRect.right, 200);
	SetRect(s, 0, 0, width, 200);
	CopyBits(gBackWind^.portbits, GrafPtr(savePat)^.portBits, s, r, 
		srcCopy, Nil);
	UnlockPixels(savePat^.portPixMap);
	SetGWorld(saveGW, saveGD);
	SetPort(gBackWind);
	k := 0;
	pi := acos(-1);
	mysound := GetNamedResource('snd ', 'Wrap');
	wrapping := false;
	End;

Procedure ResetMbar;

	Begin
	LMSetMBarHeight(save_mbar);
	DiffRgn(GrayRgn, mBarRgn, GrayRgn); { remove the menu bar from desktop }
	DisposeRgn(mBarRgn);			 { dispose the bar region }
	DrawMenuBar;
	End;

Procedure DrawBall;

Var
	saveGD: GDHandle;
	saveGW: GWorldPtr;
	offRect: rect;
	tmpRect: rect;
	delta: point;
	offscreen: gworldPtr;
	wrap: boolean;

	Begin
	newPos.h := oldPos.h + 2;
	y := 50 + 50 * sin((newPos.h + k) / 30);
	newPos.v := trunc(y);
	If newPos.h > gBackWind^.portRect.right - width Then
		Begin
		k := remainder(newPos.h + k, pi * 60);
		newPos.h := 0;
		y := 50 + 50 * sin((newPos.h + k) / 30);
		newPos.v := trunc(y);
		End;
	wrap := (newPos.h <= gBackWind^.portRect.right - width) And 
		(newPos.h + width > gBackWind^.portRect.right - width);
	If wrap And (Not wrapping) Then
		Begin
		SoundDispose;
		BackgroundSound;
		wrapping := true;
		End
	Else If wrapping And (Not wrap) Then
		wrapping := false;
	SetRect(newBox, newPos.h, newPos.v, newPos.h + width, newPos.v + height);
	UnionRect(newBox, oldBox, mainRect);
	GetGWorld(saveGW, saveGD);
	{ mainRect top and left now become the offsets since newGworld }
	{ sets offscreen starting at 0,0 }
	delta.h := mainRect.left;
	delta.v := mainRect.top;
	offrect := mainRect;
	offsetRect(offrect, -delta.h, -delta.v);
{$IFC UNDEFINED THINK_PASCAL}
	Err := newGWorld(offscreen, 0, offrect, Nil, Nil, 0);
{$ELSEC}
	Err := newGWorld(offscreen, 0, offrect, Nil, Nil, []);
{$ENDC}
	If Err <> noErr Then
		ExitToShell;
	If Not LockPixels(offscreen^.portPixMap) Then
		ExitToShell;
	SetGWorld(offscreen, Nil);
	CopyBits(grafptr(savepat)^.portbits, grafptr(offscreen)^.portbits, 
		mainRect, offRect, srcCopy, Nil);
	tmpRect := newBox;
	OffsetRect(tmpRect, -delta.h, -delta.v);
	FillOval(tmpRect, qd.black);
	SetGWorld(saveGW, saveGD);
	SetPort(gBackWind);
	CopyBits(grafptr(offscreen)^.portbits, gBackWind^.portBits, 
		offRect, mainRect, srcCopy, Nil);
	If wrap Then
		Begin
		tmpRect := mainRect;
		OffsetRect(tmpRect, width - gBackWind^.portRect.right, 0);
		CopyBits(grafPtr(offscreen)^.portbits, gBackWind^.portBits, 
			offRect, tmpRect, srcCopy, Nil);
		End;
	UnlockPixels(offscreen^.portPixMap);
	DisposeGworld(offscreen);
	oldPos := newPos;
	oldBox := newBox;
	If wrap Then
		Delay(1, ticks);
	End;

	
Begin
InitToolbox;
RemoveMbar;
SetBackground;
SetForeground;
InitGlobals;
SetPort(gBackWind);	
repeat 
	DrawBall;
until button;
ResetMbar;
End.


Copyright © 1997 Bill Catambay. All Rights Reserved.

Updated: April 15, 1997