From: keith@taligent.com (Keith Rollin)
Newsgroups: comp.sys.mac.programmer
Subject: Re: menubar in dialogs (long)
Date: 29 Jul 92 08:17:31 GMT
Organization: Taligent

In article <kaas-280792151810@jojo.cc.ruu.nl>, kaas@cc.ruu.nl (Dick Kaas)
writes:
> 
> I'm planning to write a TCL pane for a menu-bar.
> 
> Is there a simple way to implement a menubar into dialogs  (like WP and
> superboomerang) without having to write a complete menu package by
> yourself?
> 
> I have looked at the low memory globals, such as MenuBarHook etc. None of
> these seem useful.
>

I did one of these. I wouldn't consider the approach I took to be "simple."
Although I wrote the bulk of it in a couple of nights, they were long nights. I
had to re-implement and wrap up a lot of stuff. My first attempt that tried
setting the origin of the window manager port at strategic times didn't work
because of a bug in System 7.0's background saving and restoring routines.

I did it as a control to make it easy for others to use. It's included below.

How to use: create a control item in your DITL, and specify the CDEF shown
below. When the user clicks on the item, the dialog manager will call
TrackControl. If you are writing a TCL pane, then call TrackControl yourself.
The control will track the menus and leave the chosen item in contrlValue, so
all you have to do is call GetCtlValue to get the chosen menu item.

If you want to manipulate the menubar, use one of the macros starting with a "Z"
included below. Those macros will call the CDEF directly with a custom message
number. Please note that for the CallIt macro to work, the CDEF must be marked
as locked. If you don't want to lock your CDEF, then you'll need a more
elaborate way to call the CDEF than just dereferencing the handle and jumping.
As an example of what you might need, I've included a commented-out function at
the end of this posting that calls the standard MBDF directly.

A note: pay no attention to the UseGlobals and DoneWithGlobals stuff. That's for
an MPW package that allows globals in standalone code. If you are using THINK C,
don't worry about it. If you are using MPW, use the techniques shown in Technote
#256.

(BTW: as you'll see below, MenubarHook _was_ useful!)

As a final note, I found it useful to write a wrapper for the standard MDEF.
Because the below CDEF essentially works by using popup menus, you need to have
fine control over where the popup menus show up. The standard MBDF will try to
position popup menus so that as much of them will appear on the screen as
possible. However, we don't want that with our menubar CDEF; we want to the top
of the menu to be right below the menubar. Therefore, I wrap up the standard
MDEF with one of my own that first calls the standard MDEF, and then checks to
see if the message handled was mPopUpMsg. If so, the top of the menu is adjusted
by executing "menuRect->top = *itemID;".

Sorry for the lack of any comments in the source code...

-------------
MenubarCDEF.h
-------------

#ifndef THINK_C
#include <Controls.h>
#endif

#define kStayPutMDEF	90

#define clearMenuBar	128
#define deleteMenu		129			// param = ID of menu to remove
#define drawMenuBar		130
#define flashMenuBar	131			// param = ID of menu to flash
#define getMenuBar		132			// result = handle to custom data
#define getMHandle		133			// param = menu ID, result = menuHandle
#define hiliteMenu		134			// param = ID of menu to hilite
#define insertMenu		135			// param = menuHandle, varCode = before menu ID
#define menuKey			136			// param = char, result = same as MenuKey
#define menuSelect		137			// param = startPt, result = same as MenuSelect
#define setMenuBar		138			// param = result from getMenuBar

typedef pascal long (*CDEFProc)(short varCode, ControlHandle theControl,
									short msg, long param);

#define CallIt(ctl) ((CDEFProc) *((**ctl).contrlDefProc))

#define ZClearMenuBar(ctl)			CallIt(ctl)(0, ctl, clearMenuBar, 0)
#define ZDeleteMenu(ctl, menuID)	CallIt(ctl)(0, ctl, deleteMenu, menuID)
#define ZDrawMenuBar(ctl)			CallIt(ctl)(0, ctl, drawMenuBar, 0)
#define ZFlashMenuBar(ctl, menuID)	CallIt(ctl)(0, ctl, flashMenuBar, menuID)
#define ZGetMenuBar(ctl)			(Handle) CallIt(ctl)(0, ctl, getMenuBar, 0)
#define ZGetMHandle(ctl, menuID)	(MenuHandle) CallIt(ctl)(0, ctl, getMHandle,
menuID)
#define ZHiliteMenu(ctl, menuID)	CallIt(ctl)(0, ctl, hiliteMenu, 0)
#define ZInsertMenu(ctl, mh, id)	CallIt(ctl)(id, ctl, insertMenu, mh)
#define ZMenuKey(ctl, key)			CallIt(ctl)(0, ctl, menuKey, key)
#define ZMenuSelect(ctl, startPt)	CallIt(ctl)(0, ctl, menuSelect, startPt)
#define ZSetMenuBar(ctl, mbar)		CallIt(ctl)(0, ctl, setMenuBar, mbar)



-------------
MenubarCDEF.c
-------------


/*
	To do:
	
	% Support hierarchicals
	% Support color
*/

#include "MetaGlobal.h"
#include "MenubarCDEF.h"

#ifdef THINK_C
	#include <SetUpA4.h>
	Ptr GetA0(void) = { 0x2008 };
#else
	#include <Types.h>
	#include <Memory.h>
	#include <Menus.h>
	#include <OSEvents.h>			// For EvQPtr
	#include <Resources.h>			// For GetResource
	#include <Script.h>				// For GetMBarHeight
	#include <ToolUtils.h>			// For HiWord, LoWord
	#include <Windows.h>			// we return inMenubar as our part
	#include "SAGlobals.h"
	
	#define MenuHook (*(ProcPtr*) 0xA30)
	#define MenuList (*(Handle*) 0xA1C)
#endif

#define NIL				NULL
#define kTopMargin		1
#define kBottomMargin	1
#define kTextMargin		8


typedef struct {
	MenuHandle		menu;
	short			menuLeft;
} MenuRec, *MenuRecPtr;


typedef struct {
	MenuHandle		menu;
	short			reserved;
} HMenuRec, *HMenuRecPtr;


typedef struct {
	short			lastMenu;
	short			lastRight;
	short			mbResID;
	MenuRec			menus[1];
	//	short			lastHMenu;
	//	PixMapHandle	menuTitleSave;
	//	MenuRec			hmenus[1];
} MenuBar, *MenuBarPtr, **MenuBarHdl;


ProcPtr			gOldMenuHook;
ControlHandle	gControl;
MenuBarHdl		gMenuList;
short			gTheMenu;			// _Index_ of currently hilighted menu
short			gNormalHeight;
FontInfo		gFontInfo;
short			gBaseline;


pascal long		main(short varCode, ControlHandle theControl, short msg, long
param);

void			DrawMyControl(short part);
short			TestMyControl(Point location);
void			InitMyControl(void);
void			DisposeMyControl(void);

void			DoClearMenuBar(void);
void			DoDeleteMenu(short menuID);
void			DoDrawMenuBar(void);
void			DoFlashMenuBar(short menuID);
MenuBarHdl		DoGetMenuBar(void);
MenuHandle		DoGetMHandle(short menuID);
void			DoHiliteMenu(short menuID);
void			DoInsertMenu(MenuHandle menu, short before);
long			DoMenuKey(char theKey);
long			DoMenuSelect(Point startPt);
void			DoSetMenuBar(MenuBarHdl menuBar);

void			DrawMenuTitle(short menuIndex);
short			FindHitMenu(Point location);		// returns menuIndex (or -1)
short			GetFreeHMenuID(void);
Rect			GetHitRect(short menuIndex);
Rect			GetInvertRect(short menuIndex);
short			GetMenuCount(void);
MenuHandle		GetNthMenu(short index);
Point			GetTitleLocation(short menuIndex);
short			IDToIndex(short);
short			IndexToID(short);
void			MyMenuHook(void);
void			SwapMenuBars(void);

//--------------------------------------------------------------------------------

pascal long	main(short varCode, ControlHandle theControl, short msg, long param)
{
	long		result;
	Ptr			oldA5;
	char		oldState;
	Handle		ourHandle;
	Ptr			ourPtr;

	ourPtr = GetA0();

#ifdef THINK_C
	RememberA0();
	SetUpA4();
#else
	oldA5 = UseGlobals();
#endif

	ourHandle = RecoverHandle(ourPtr);
	oldState = HGetState(ourHandle);
	HLock(ourHandle);
	
	result = 0;
	gControl = theControl;
	GetFontInfo(&gFontInfo);
	gBaseline = kTopMargin + gFontInfo.leading + gFontInfo.ascent;

	if (msg < 128) {
		switch (msg) {
			case drawCntl:
				DrawMyControl((short) param);
				break;
			case testCntl:
				result = TestMyControl(*(Point*) &param);
				break;
			case initCntl:
				InitMyControl();
				break;
			case dispCntl:
				DisposeMyControl();
				break;
			case calcCntlRgn:
			case calcThumbRgn:
				RectRgn((RgnHandle) param, &(**gControl).contrlRect);
				result = 1;
				break;
		}
	} else {
		switch (msg) {
			case clearMenuBar:
				DoClearMenuBar();
				break;
			case deleteMenu:
				DoDeleteMenu((short) param);
				break;
			case drawMenuBar:
				DoDrawMenuBar();
				break;
			case flashMenuBar:
				DoFlashMenuBar((short) param);
				break;
			case getMenuBar:
				result = (long) DoGetMenuBar();
				break;
			case getMHandle:
				result = (long) DoGetMHandle((short) param);
				break;
			case hiliteMenu:
				DoHiliteMenu((short) param);
				break;
			case insertMenu:
				DoInsertMenu((MenuHandle) param, varCode);
				break;
			case menuKey:
				result = DoMenuKey((char) param);
				break;
			case menuSelect:
				result = DoMenuSelect(*(Point*) &param);
				break;
			case setMenuBar:
				DoSetMenuBar((MenuBarHdl) param);
				break;
		}
	}

#ifdef THINK_C
	RestoreA4();
#else
	DoneWithGlobals(oldA5);
#endif
	
	HSetState(ourHandle, oldState);

	return result;
}


//--------------------------------------------------------------------------------

void	DrawMyControl(short part)
{
#pragma unused (part)

	if ((**gControl).contrlVis != false) {
		DoDrawMenuBar();
	}
}


//--------------------------------------------------------------------------------

short	TestMyControl(Point location)
{
	short		hitPart;
	
	hitPart = 0;
	if (FindHitMenu(location) >= 0) {
		hitPart = inMenuBar;
		if (Button()) {
			DoMenuSelect(location);
			if ((**gControl).contrlValue == 0) {
				hitPart = 0;
			}
		}
	}

	return hitPart;
}


//--------------------------------------------------------------------------------

void	InitMyControl()
{
	Handle		menuBar;

	gOldMenuHook = NIL;
	gMenuList = NIL;
	gTheMenu = -1;
	gNormalHeight = GetMBarHeight();
	(**gControl).contrlRect.bottom = (**gControl).contrlRect.top + gBaseline
						+ gFontInfo.descent + gFontInfo.leading + kBottomMargin;

	GetMBarHeight() = 0;
	menuBar = GetNewMBar((**gControl).contrlMin);
	if (menuBar != NIL) {
		gMenuList = (MenuBarHdl) menuBar;
	} else {
		menuBar = GetMenuBar();
		ClearMenuBar();
		gMenuList = (MenuBarHdl) GetMenuBar();
		SetMenuBar(menuBar);
		DisposeHandle(menuBar);
	}
	GetMBarHeight() = gNormalHeight;
}


//--------------------------------------------------------------------------------

void	DisposeMyControl()
{
	DisposeHandle((Handle) gMenuList);
}


//--------------------------------------------------------------------------------

void	DoClearMenuBar()
{
	SwapMenuBars();
	ClearMenuBar();
	SwapMenuBars();
}


//--------------------------------------------------------------------------------

void	DoDeleteMenu(short menuID)
{
	if (IndexToID(gTheMenu) == menuID)
		gTheMenu = -1;

	SwapMenuBars();
	DeleteMenu(menuID);
	SwapMenuBars();
}


//--------------------------------------------------------------------------------

void	DoDrawMenuBar()
{
	Rect			frame;
	short			loop;

	PenNormal();
	frame = (**gControl).contrlRect;
	FrameRect(&frame);

	gTheMenu = -1;
	for (loop = GetMenuCount() - 1; loop >= 0; loop--) {
		DrawMenuTitle(loop);
	}
}


//--------------------------------------------------------------------------------

void	DoFlashMenuBar(short menuID)
{
	Rect		bounds;

	if (menuID != 0)
		DoHiliteMenu(menuID);
	else {
		bounds = (**gControl).contrlRect;
		InsetRect(&bounds, 1, 1);
		InvertRect(&bounds);
	}
}


//--------------------------------------------------------------------------------

MenuBarHdl	DoGetMenuBar()
{
	return gMenuList;
}


//--------------------------------------------------------------------------------

MenuHandle	DoGetMHandle(short menuID)
{
	short	menuIndex;
	
	menuIndex = IDToIndex(menuID);
	if (menuIndex >= 0)
		return GetNthMenu(menuIndex);
	else
		return NIL;
}


//--------------------------------------------------------------------------------

void	DoHiliteMenu(short menuID)
{
	short		menuIndex;
	Rect		invertRect;
	
	menuIndex = IDToIndex(menuID);
	if (menuIndex != gTheMenu) {
		if (menuIndex >= 0) {
			invertRect = GetInvertRect(menuIndex);
			InvertRect(&invertRect);
		}
		gTheMenu = menuIndex;
	}
}


//--------------------------------------------------------------------------------

void	DoInsertMenu(MenuHandle menu, short before)
{
	SwapMenuBars();
	InsertMenu(menu, before);
	SwapMenuBars();
}


//--------------------------------------------------------------------------------

long	DoMenuKey(char theKey)
{
	char			upperKey[2];
	short			loop;
	MenuHandle		menu;
	short			mItems;
	short			menuItem;
	short			key;

	upperKey[0] = 1;
	upperKey[1] = theKey;
	UprString((StringPtr) &upperKey, true);

	for (loop = GetMenuCount() - 1; loop >= 0; loop--) {
		menu = GetNthMenu(loop);
		mItems = CountMItems(menu);
		for (menuItem = 1; menuItem <= mItems; menuItem++) {
			if (menuItem > 31 || (((**menu).enableFlags & (1 << menuItem)) != 0)) {
				GetItemCmd(menu, menuItem, &key);
				if (key == upperKey[1]) {
					return ((long) (**menu).menuID << 16) + menuItem;
				}
			}
		}
	}

	return 0;
}


//--------------------------------------------------------------------------------

long	DoMenuSelect(Point startPt)
{
#pragma unused (startPt)

	Point			currentLocation;
	short			oldMenuIndex;
	short			newMenuIndex;
	Rect			invertRect;
	Point			where;
	MenuHandle		menu;
	long			menuAndItem;
	short			oldMenuID;
	Handle			myMDEF;

	menuAndItem = 0;
	while (Button()) {
		GetMouse(&currentLocation);
		oldMenuIndex = gTheMenu;
		newMenuIndex = FindHitMenu(currentLocation);
		if (oldMenuIndex != newMenuIndex) {
			gTheMenu = newMenuIndex;
			DrawMenuTitle(oldMenuIndex);
			if (newMenuIndex >= 0) {
				DrawMenuTitle(newMenuIndex);
	
				invertRect = GetInvertRect(newMenuIndex);
				where.v = invertRect.bottom + 1;
				where.h = invertRect.left + 1;
				LocalToGlobal(&where);
	
				gOldMenuHook = MenuHook;
				MenuHook = (ProcPtr) MyMenuHook;
	
				menu = GetNthMenu(newMenuIndex);
	
				oldMenuID = (**menu).menuID;
				(**menu).menuID = GetFreeHMenuID();
	
				myMDEF = GetResource('MDEF', kStayPutMDEF);
				if (myMDEF != NIL) {
					(**(StdHeaderHdl) myMDEF).refCon = (long) (**menu).menuProc;
					(**menu).menuProc = myMDEF;
				}
	
				InsertMenu(menu, hierMenu);
				menuAndItem = PopUpMenuSelect(menu, where.v, where.h, 1);
				DeleteMenu((**menu).menuID);
	
				(**menu).menuID = oldMenuID;
	
				if (myMDEF != NIL) {
					(**menu).menuProc = (Handle) (**(StdHeaderHdl) myMDEF).refCon;
					(**(StdHeaderHdl) myMDEF).refCon = (long) NIL;
				}
	
				MenuHook = gOldMenuHook;
	
				if (HiWord(menuAndItem) != 0)
					menuAndItem = ((long) oldMenuID << 16) + (short) menuAndItem;
	
				(**gControl).contrlValue = HiWord(menuAndItem);
				(**gControl).contrlMax = LoWord(menuAndItem);
			}
		}
	}

	if (gTheMenu != -1) {
		oldMenuIndex = gTheMenu;
		gTheMenu = -1;
		DrawMenuTitle(oldMenuIndex);
	}

	return menuAndItem;
}


//--------------------------------------------------------------------------------

void	DoSetMenuBar(MenuBarHdl menuBar)
{
	DisposeHandle((Handle) gMenuList);
	gMenuList = menuBar;
	DoDrawMenuBar();
}


//--------------------------------------------------------------------------------

void	DrawMenuTitle(short menuIndex)
{
	Rect			invertRect;
	Point			titleLocation;
	MenuHandle		menu;

	if ((menuIndex >= 0) && (menuIndex < GetMenuCount())) {

		invertRect = GetInvertRect(menuIndex);
		EraseRect(&invertRect);

		menu = GetNthMenu(menuIndex);
		if ((((**menu).enableFlags & 1) == 0) || ((**gControl).contrlHilite == 255))
			TextMode(grayishTextOr);
		else
			TextMode(srcOr);
		
		titleLocation = GetTitleLocation(menuIndex);
		MoveTo(titleLocation.h, titleLocation.v);
		HLock((Handle) menu);
		DrawString((**menu).menuData);
		HUnlock((Handle) menu);

		if (gTheMenu == menuIndex)
			InvertRect(&invertRect);
	}
}


//--------------------------------------------------------------------------------

short	FindHitMenu(Point location)
{
	short			loop;
	Rect			hitRect;

	for (loop = GetMenuCount() - 1; loop >= 0; loop--) {
		hitRect = GetHitRect(loop);
		if (PtInRect(location, &hitRect)) {
			break;
		}
	}

	return loop;		// returns -1 if no hit
}


//--------------------------------------------------------------------------------

short	GetFreeHMenuID(void)
{
	short	index;

	for (index = 235-20; index > 0; index--) {
		if (GetMHandle(index) == NIL)
			return index;
	}
	return -1;
}


//--------------------------------------------------------------------------------

Rect	GetHitRect(short menuIndex)
{
	Rect		bounds;
	RectPtr		rectPtr;
	MenuBarPtr	menuBarPtr;
	
	rectPtr = &(**gControl).contrlRect;
	menuBarPtr = *gMenuList;

	bounds.top = rectPtr->top + 1;
	bounds.bottom = rectPtr->bottom - 1;
	bounds.left = rectPtr->left + menuBarPtr->menus[menuIndex].menuLeft;
	menuIndex++;
	bounds.right = rectPtr->left + ((menuIndex < GetMenuCount())
										? menuBarPtr->menus[menuIndex].menuLeft
										: menuBarPtr->lastRight);
	
	return bounds;
}


//--------------------------------------------------------------------------------

Rect	GetInvertRect(short menuIndex)
{
	Rect		bounds;
	
	bounds = GetHitRect(menuIndex);
	bounds.left--;
	bounds.right += 4;
	return bounds;
}


//--------------------------------------------------------------------------------

short	GetMenuCount()
{
	return (**gMenuList).lastMenu / (short) sizeof(MenuRec);
}


//--------------------------------------------------------------------------------

MenuHandle	GetNthMenu(short index)
{
	return (**gMenuList).menus[index].menu;
}


//--------------------------------------------------------------------------------

Point	GetTitleLocation(short menuIndex)
{
	Point	result;
	
	result.h = (**gMenuList).menus[menuIndex].menuLeft + kTextMargin;
	result.v = (**gControl).contrlRect.top + gBaseline;
	
	return result;
}


//--------------------------------------------------------------------------------

short	IDToIndex(short menuID)
{
	short		loop;

	for (loop = GetMenuCount() - 1; loop >= 0; loop--) {
		if ((**GetNthMenu(loop)).menuID == menuID)
			break;
	}
	return loop;
}


//--------------------------------------------------------------------------------

short	IndexToID(short menuIndex)
{
	if ((menuIndex >= 0) && (menuIndex < GetMenuCount()))
		return (**GetNthMenu(menuIndex)).menuID;
	else
		return 0;
}


//--------------------------------------------------------------------------------

void	MyMenuHook()
{
	typedef void (*MenuHookProc)(void);

	GrafPtr		oldPort;
	Point		mouseLocation;
	short		hitMenu;
	Ptr			oldA5;

#ifdef THINK_C
	SetUpA4();
#else
	oldA5 = UseGlobals();
#endif

	if (gOldMenuHook != NIL)
		((MenuHookProc) gOldMenuHook)();

	GetPort(&oldPort);
	SetPort((**gControl).contrlOwner);
	GetMouse(&mouseLocation);
	hitMenu = FindHitMenu(mouseLocation);
	if ((hitMenu >= 0) && (hitMenu != gTheMenu)) {
		PostEvent(mouseUp, 0);
	}
	SetPort(oldPort);

#ifdef THINK_C
	RestoreA4();
#else
	DoneWithGlobals(oldA5);
#endif
}


//--------------------------------------------------------------------------------

void	SwapMenuBars()
{
	MenuBarHdl		oldMenuBar;
	
	oldMenuBar = (MenuBarHdl) MenuList;
	MenuList = (Handle) gMenuList;
	gMenuList = oldMenuBar;
}





#if 0

void			CalcMenuPositions(short index);
long			CallMBDF(short message, short param1, long param2);

//--------------------------------------------------------------------------------

void	CalcMenuPositions(short index)
{
	SwapMenuBars();
	CallMBDF(2, 0, (index+1) * 6);
	SwapMenuBars();
}


//--------------------------------------------------------------------------------

long	CallMBDF(short message, short param1, long param2)
{
	typedef pascal long (*MBDFProc)(short selector, short message, short
parameter1,
										long parameter2);
	Handle		mbdfHandle;
	short		mbResID;
	short		resID;
	short		mbVariant;
	char		oldState;
	long		result;
	
	mbResID = (**gMenuList).mbResID;
	resID = mbResID >> 3;
	mbVariant = mbResID & 0x0007;
	mbdfHandle = GetResource('MBDF', resID);
	if (mbdfHandle != NIL) {
		if (*mbdfHandle == NIL)  {
			LoadResource(mbdfHandle);
		}
		if (*mbdfHandle != NIL) {
		
			oldState = HGetState(mbdfHandle);
			HLock(mbdfHandle);
			result = ((MBDFProc) *mbdfHandle)(mbVariant, message, param1, param2);
			HSetState(mbdfHandle, oldState);
			return result;
		}
	}

	SysError(dsMBarNFnd);
}
#endif


--
Keith Rollin
Phantom Programmer
Taligent, Inc.
Newsgroups: comp.sys.mac.programmer