Back in August of 1989 there was a discussion here on methods for implementing a "Windows" menu, one with an entry for each window currently displayed by the application. The act of selection would bring that window to the fore, etc.
David Phillip Oster asked: "what happens if you have multiple files open, all with the same name?" and then suggested "perhaps the simplest solution is to just use the item position in the windows menu, rather than the title, to determine which window the user wishes to select -- a problem with this is that you can get identical menu items, but at least the program can distinguish among them, if not the user."
This posting describes a simple scheme to implement this paradigm. Please feel free to use your "junk" key if you are not interested.
The scheme comprises two parts:
1. A linked list of the windows, sorted by the window names, is kept from a
global cell through a window pointer variable in each window data area.
Note: this list is in addition to the Window Manager's list.
2. A small integer in the window data area holds the index in the menu
of that window's item. I called it MRefNum ("Menu Reference Number),
for lack of a better name.
The management algorithms are:
When a new window is created, the proper place in the linked list must be found. We scan the linked list until we find either the end of the list or a window whose name is lexically greater than that of our new window. We are then interested in the item BEFORE that position which is either the root or a window whose name is lexically less than the new name.
The MRefNum of this preceding entry determines the position in the menu that the new window's name is inserted. It is also incremented and becomes the MRefNum of the new window. The new window is inserted into the chain at this point, and all succeeding windows in the chain have their MRefNum's incremented, corresponding to the fact that their menu entries were pushed down by the insert of the new window name.
If the previous item was the root, the new MRefNum becomes 1, and the same things are done.
When a window is destroyed, the menu item corresponding to its MRefNum is deleted, the window is removed from the chain, and all succeeding windows in the chain have their MRefNum's decremented.
I did have an algorithm for changing the name, which broke down into two possibilities corresponding to moving a name UP in the chain (incrementing the MRefNums between the new and old position) or moving a name DOWN in the chain (decrementing the MRefNums between the old and new position) but in the actual program I deleted and re-inserted the window :-)
Getting from a window to it's menu item is easy, as the MRefNum designates the appropriate menu index. Getting from a menu item to the window is just searching for the appropriate MRefNum value.
Here's the code for creating a window:
/* Insert a window into the proper point in the window chain.
* Also makes appropriate entry in the window menu.
*/
InsertWind(RWPtr newwind)
{
RWPtr lastw = nil;
RWPtr nextw = AFWind; /* the root */
short refn;
while ( (nil!=nextw) && (0<CompHand(newwind->wname,nextw->wname)) ) {
lastw = nextw;
nextw = nextw->next;
}
refn = (nil!=lastw)?lastw->mrefn:0;
newwind->mrefn = refn+1;
newwind->next = nextw;
if (lastw!=nil)
lastw->next = newwind;
else
AFWind = newwind;
InsMenuItem(WMenu,"\pa",WMBASE+refn);
SetItem(WMenu,WMBASE+newwind->mrefn,*newwind->wname);
while (nil != nextw) {
nextw->mrefn++;
nextw = nextw->next;
}
}
Here's the code for destroying the window:
/* This routine removes a window from the window chain.
* It also removes the appropriate window menu entry.
*/
RemoveWind(RWPtr mortwind)
{
RWPtr lastw = nil;
RWPtr nextw = AFWind; /* the root */
while ( (nil!=nextw) && (nextw!=mortwind) ) {
lastw = nextw;
nextw = nextw->next;
}
if (nil!=nextw) {
if (nil!=lastw)
lastw->next = nextw->next;
else
AFWind = nextw->next;
DelMenuItem(WMenu,WMBASE+mortwind->mrefn);
while (nil!=nextw) {
nextw->mrefn--;
nextw = nextw->next;
}
} else
SysBeep(30);
}
Here's some code (case WIMENU) that goes from the menu item to the window.
/* Process selection from menu.
* Apple menu:
* Note: MUST do DA stuff so MF can switch from Apple menu...
* Note: no "about" box is implemented.
* FILE menu:
* ...
* SHOW menu:
* Flip view bits or change displayed resource type.
* WIND menu:
* Iconize/deiconize or bring window to front.
*/
DoMenu(int menuparam)
{
char daname[256];
int menuitem = LoWord(menuparam);
WindowPtr windp = FrontWindow();
RWPtr mywind = windp;
short wkind = WindowKind(windp);
switch ( HiWord(menuparam) ) {
case APMENU:
if ( menuitem == 1 )
SysBeep(30);
else {
GetItem(AMenu,menuitem,daname);
OpenDeskAcc(daname);
AdjustMenus();
}
break;
case EDMENU:
SystemEdit(menuitem-1);
break;
case FIMENU:
switch (menuitem) {
case FMOPEN:
OpenWindow();
break;
case FMCLOS:
if (wkind < 0)
CloseDeskAcc(wkind);
else
KillWindow(windp);
break;
case FMPSET:
PrintSetup(windp);
break;
case FMPRNT:
PrintWindow(windp);
break;
case FMQUIT:
ExitToShell();
}
break;
case SHMENU:
if (nil != windp) {
if (menuitem >= SMBASE) {
OpenType = menuitem-SMBASE;
SetWindType(windp,OpenType);
} else switch (menuitem) {
case SMRNUM:
mywind->wview.show.rnum = mywind->wview.show.rnum ^ 1;
break;
case SMRNAME:
mywind->wview.show.rname = mywind->wview.show.rname ^ 1;
break;
case SMMASK:
mywind->wview.show.mask = mywind->wview.show.mask ^ 1;
}
PostWindowSize(mywind);
SetPort(windp);
InvalRect(&windp->portRect);
AdjustMenus();
}
break;
case WIMENU:
if (menuitem<WMBASE) {
if (nil != windp) {
switch (menuitem) {
case WMICZE:
if (mywind->wview.show.icize)
DeIconizeWindow(mywind);
else
IconizeWindow(mywind);
}
SetPort(windp);
InvalRect(&windp->portRect);
AdjustMenus();
}
} else {
for ( mywind=AFWind ; mywind!=nil ; mywind=mywind->next)
if (WMBASE+mywind->mrefn == menuitem)
SelectWindow( (WindowPtr) mywind);
}
}
HiliteMenu(0);
}
Here's some code (last for loop in this proc) that gets from window to menu:
/* Set checkmarks of view and window menus according to mode of front window.
* There are three cases:
* If FrontWindow() is nil then there are no windows open (degenerate case).
* If FrontWindow() is not nil but windowKind is negative then the window is
* a Desk Accessory opened by (single) Finder or a DA opened by MultiFinder
* within our application heap (option launch). In this case we want to
* make the EDIT menu active but not any of our own menus, since the window
* will not have the data items our code assumes are there.
* If windowKind is positive we assume it is one of our own normal windows,
* since we do not have any modeless dialogs.
*/
AdjustMenus()
{
WindowPtr windp = FrontWindow();
RWPtr mywind = windp;
short indx;
if (nil == windp) {
DisableItem(FMenu,FMCLOS);
DisableItem(FMenu,FMPSET);
DisableItem(FMenu,FMPRNT);
DisableItem(EMenu,0);
DisableItem(SMenu,0);
CheckItem(SMenu,SMRNUM,false);
CheckItem(SMenu,SMRNAME,false);
CheckItem(SMenu,SMMASK,false);
for (indx=0 ; indx<NRType ; indx++)
CheckItem(SMenu,SMBASE+indx,false);
DisableItem(WMenu,0);
CheckItem(WMenu,WMICZE,false);
} else if ( WindowKind(windp) < 0 ) {
EnableItem(FMenu,FMCLOS);
DisableItem(FMenu,FMPSET);
DisableItem(FMenu,FMPRNT);
EnableItem(EMenu,0);
DisableItem(SMenu,0);
CheckItem(SMenu,SMRNUM,false);
CheckItem(SMenu,SMRNAME,false);
CheckItem(SMenu,SMMASK,false);
for (indx=0 ; indx<NRType ; indx++)
CheckItem(SMenu,SMBASE+indx,false);
DisableItem(WMenu,0);
CheckItem(WMenu,WMICZE,false);
} else {
EnableItem(FMenu,FMCLOS);
EnableItem(FMenu,FMPSET);
EnableItem(FMenu,FMPRNT);
DisableItem(EMenu,0);
EnableItem(SMenu,0);
CheckItem(SMenu,SMRNUM,mywind->wview.show.rnum);
CheckItem(SMenu,SMRNAME,mywind->wview.show.rname);
CheckItem(SMenu,SMMASK,mywind->wview.show.mask);
for (indx=0 ; indx<NRType ; indx++)
CheckItem(SMenu,SMBASE+indx,indx==mywind->wdata.dtype);
EnableItem(WMenu,0);
CheckItem(WMenu,WMICZE,mywind->wview.show.icize);
}
for ( mywind=AFWind ; mywind!=nil ; mywind=mywind->next)
CheckItem(WMenu,WMBASE+mywind->mrefn,( (RWPtr) windp == mywind ));
DrawMenuBar();
}
These are subroutines used by the above:
/* Compare string handles
*/
CompHand(Handle s1,Handle s2)
{
int ans;
HLock(s1);
HLock(s2);
ans = IUCompString(*s1,*s2);
HUnlock(s1);
HUnlock(s2);
return(ans);
}
/* Get the windowKind field from the window record. This is used to
* decide if a window belongs to the program, or if it is a desk
* accessory called either from the old Finder environment or from
* the MultiFinder environment with the option key down.
*/
WindowKind(WindowPtr windp)
{
int wkind = 0;
if (nil != windp)
wkind = ((WindowPeek) windp)->windowKind;
return(wkind);
}
Actually, it occurs to me that there is no need to explicitly store the MRefNum at all, as it should be identical to the position of that window in the list (that is, reading the list should return 1, 2, 3, etc). Extension to remove the MRefNum cell is left as an exercise to the reader...
This started out to be an "is it possible to write an INIT in PASCAL?" project to see if I could do It. The answer is yes and no. It can be done but you will need either some inline assembly or some assembly glue code. The INIT I wrote is a jGNEfilter that checks to see if the user hit one of the extended keyboard function keys. The unit "newJGNEFilter" is not real working code. It is only a framework of what you need to do. I myself have not finished the code yet so I am not sure if it will be the way I go. When my INIT is finished I will update this article to include full working code.
{This unit is the code that will install a jGNEFilter patch.}
{Written by Matthew Xavier Mora}
unit install;
interface
procedure main;
implementation
procedure ShowINIT (iconID, moveX: Integer);
EXTERNAL;
procedure main;
var
newjgne: Handle;
addr: longint;
JGNE: ptr;
jgneptr: ^ptr;
begin
SetZone(SystemZone);
newjgne := Get1Resource('CODE', 128);
if newjgne <> nil then
begin
DetachResource(newjgne);
HLock(newjgne);
JGNE := pointer($29A);
BlockMove(JGNE,ptr(ord(newjgne^)+10),4);{move jgne address into header of newcode}
jgneptr := pointer($29A);
jgneptr^ := pointer(newjgne^);
ShowINIT(128, -1);
end;
end;
end.
unit newJGNEFilter;
interface
procedure main;
implementation
function GetA1 (dummy: longint): longint;
external;
function GetD0 (dummy: longint): integer;
external;
procedure Setresult (dummy: integer);
external;
procedure DoJsr (addr: ProcPtr);
inline
$205F, $4E90;
procedure DoJmp (addr: ProcPtr);
inline
$4CDF, $1CE0, $4E5E, $205F, $4ED0;
{MOVEM.L (A7)+,D5-D7/A2-A4}
{UNLK A6}
{MOVEA.L (A7)+,A0}
{JMP (A0)}
procedure main;
var
addr, oldjgne: procptr;
dummy: integer;
Eventmessage: longint;
event: eventrecord;
hasevent: boolean;
Evntptr: ^eventrecord;
begin
hasevent := Boolean(GetD0(0)); {D0 contains flag of gne}
if hasevent then
begin
Evntptr := pointer(geta1(0)); {A0 contains a pointer to the eventrecord}
Eventmessage := Evntptr^.message;
{do whatever you wish }
{if you hande the event your self then set D0 to false}
{ SetD0(false);}
end;
oldjgne := procptr(ord(@main) - 6); {get address that was stored into header}
addr := procptr(oldjgne);
if addr <> nil then
DoJmp(addr); {jmp to address stored in header}
end;
by Larry Rosenstein
[Asynchronous Sound Code]
Enclosed is the source for an MPW Pascal unit that shows how to play asynchronous sounds with the Sound Manager. I have tried this unit only on System 6.0.2; supposedly there are bugs in earlier versions of the Sound Manager. This unit also doesn't check for the existence of the Sound Manager, I assume that you do this at a higher level.
I used this in a simple MacApp program that will open any file and allow you to play any snd resource in the file ansynchronously. (I started this with the idea of allowing copy & paste, but haven't gotten that far yet. If there is interest, I can post that program.)
Larry Rosenstein, Object Specialist
---
(*
A simple unit that demonstrates how to produce asynchronous sound with
the Macintosh Sound Manager. Although I am pretty confident about this code,
I don't guarantee that this code demonstrates the correct way to do things. It
does seem to work reliably.
This unit doesn't solve any of the tricky issues about using the Sound Manager.
Primarily, sound channels should be disposed of as soon as they are no longer
needed. This code does just that, but it doesn't prevent your program or a
background program from trying to make sound.
Changes:
2/16/89 Lock the sound resource; state restored in call back
gSoundPlaying is the actual handle.
Larry Rosenstein
Apple Computer, Inc.
lsr@Apple.COM
Copyright 1988-1989 Apple Computer Inc. All Rights Reserved.
*)
UNIT UAsynchSnd;
INTERFACE
USES
MemTypes, Quickdraw, OSIntf, ToolIntf;
{ call this before any other routine }
PROCEDURE InitUAsynchSnd;
{ returns TRUE if an asynchronous sound is playing }
FUNCTION IsSoundPlaying: BOOLEAN;
{ equivalent to SndPlay, but does it asynchronously; if you call this
while another sound is playing, the first one will be stopped }
FUNCTION ASynchSndPlay(sndHandle: Handle): OSErr;
{ stop the sound from playing; may be called even if no sound is
currently playing }
PROCEDURE StopAsynchSound;
{ should be called when your program exits }
PROCEDURE TerminateUAsynchSnd;
IMPLEMENTATION
VAR
gSoundPlaying: Handle;
gSoundState: SignedByte;
gSndChannel: SndChannelPtr;
PROCEDURE ChanCallBack(chan: SndChannelPtr; cmd: SndCommand); FORWARD;
FUNCTION GetA5: LONGINT; INLINE $2E8D; {MOVE.L A5,(A7)}
FUNCTION LoadA5(newA5: LONGINT): LONGINT;
INLINE $2F4D,$0004,$2A5F;
(********************)
PROCEDURE InitUAsynchSnd;
BEGIN
gSndChannel := NIL;
gSoundPlaying := NIL;
END;
FUNCTION ASynchSndPlay(sndHandle: Handle): OSErr;
VAR err: OSErr;
aCommand: SndCommand;
BEGIN
StopAsynchSound; { kill the current sound & channel }
err := noErr; { default value }
{ gSndChannel should be NIL now }
err := SndNewChannel(gSndChannel, 0, 0, @ChanCallBack);
{ We don't specify a synthesizer, since we are assuming that
the snd resource specifies one. For example, the
standard Clink-Klank snd resource doesn't use the
sampled synthesizer. }
gSoundState := HGetState(sndHandle);
MoveHHi(sndHandle);
HLock(sndHandle);
gSoundPlaying := sndHandle;
IF err = noErr THEN
err := SndPlay(gSndChannel, sndHandle, TRUE);
WITH aCommand DO BEGIN
cmd := callBackCmd;
param1 := 0;
param2 := GetA5;
END;
IF err = noErr THEN
err := SndDoCommand(gSndChannel, aCommand, FALSE);
IF err <> noErr THEN
StopAsynchSound; { flush channel; unlock sound }
AsynchSndPlay := err;
END;
PROCEDURE ChanCallBack(chan: SndChannelPtr; cmd: SndCommand);
VAR oldA5: LONGINT;
BEGIN
oldA5 := LoadA5(cmd.param2); { get the application's A5 and set it }
HSetState(gSoundPlaying, gSoundState);
gSoundPlaying := NIL;
oldA5 := LoadA5(oldA5); { restore old A5 }
END;
FUNCTION IsSoundPlaying: BOOLEAN;
BEGIN
IsSoundPlaying := gSoundPlaying <> NIL;
END;
PROCEDURE StopAsynchSound;
BEGIN
IF gSndChannel <> NIL THEN BEGIN
IF SndDisposeChannel(gSndChannel, TRUE) = noErr THEN { nothing };
gSndChannel := NIL;
END;
IF gSoundPlaying <> NIL THEN
BEGIN
HSetState(gSoundPlaying, gSoundState);
gSoundPlaying := NIL;
END;
END;
PROCEDURE TerminateUAsynchSnd;
BEGIN
StopAsynchSound;
END;
END.
(c)1990 Lim Unlimited -- All Rights Reserved
The Default CDEF is a simple aid for Macintosh programmers that draws default button outlines for any size buttons, in the proper color, in your application and in ResEdit dialog and alert templates. Push buttons, check boxes, and radio buttons can also be drawn using the window's font.
Instructions
Default is a control definition function that you can copy and paste into your application. The Default CDEF enhances the System file's standard control definition 0. If the last character of a button's title is an `@' (an at or an each symbol), the button is drawn with an outline indicating that it is the default button. The trailing `@' is not drawn with the title.
If the button is inactive, the outline is grayed out. When Color QuickDraw is available, outlines are drawn in the same color as the button's frame. If the Default CDEF is in the same file as your application's DITL resources, ResEdit will display default button outlines drawn by Default. You do not need to write any code to use Default.
A dialog's default button can be changed at any time in your application simply by changing the appropriate button titles. However, you, the programmer, are responsible for making sure there is only one default button. If you are not using a filterProc, Default makes sure that pressing the Return key or Enter key is the same as clicking in the default button. The default button is also highlighted when the Return key or Enter key is pressed. If you are using your own filterProc, you must perform these tasks.
If the last character of a control's title is an `[[florin]]', the control is drawn using the window's current font. Push buttons, check boxes, and radio buttons will look the same as if you used a CNTL resource with the useWFont variation code. This feature is especially useful for CDEVs. If you need a default button that uses the window's font, end the button's title with `[[florin]]@'.
Compatibility
Default should work on any model of Macintosh with any System version. Default is 32-bit clean. Except for drawing the outline, Default lets the System file's CDEF 0 do practically everything. Thus, Default's buttons do anything that normal buttons do.
Be warned that Default uses the contrlData field of a ControlRecord (which is okay since it is reserved for CDEFs). If your application does something strange with this field, Default will not work.
When you drag or resize a default button in ResEdit, you may notice some slight flickering. This occurs because ResEdit does not know about the outline so Default forces ResEdit to refresh portions of the window. Default only behaves this way in ResEdit. This does not occur in normal applications. Default now comes in versions with and without the ResEdit updating code. This may be helpful if you are concerned about your application's size.
Bugs
The outline will not update if the outline needs updating and the button does not, and UpdtControl or UpdtDialog is being used to update the controls. This problem does not occur with normal modal dialogs and occurs rarely with modeless dialogs. Unfortunately, there doesn't seem to be a clean solution this problem. If you have this problem, you can call Draw1Control for the default button on every update event or simply use DrawControls or DrawDialog instead.
If you have a default button and you aren't using a filterProc, pressing the Return key or Enter key will click in the default button even if the button is hidden. This is a bug in the Dialog Manager. If you have this problem, you must change the default button to a normal button before you call HideDItem or HideControl.
Please report any other bugs to any of the addresses listed below. Thanks to those programmers who did unusual things with their buttons, reported bugs, and helped make Default more robust.
History
Distribution
Default is copyrighted but it is also available free of charge. You may copy and redistribute Default provided that this documentation accompanies any redistributed copies of Default and the Default CDEF is not modified in any way.
You may include Default in any commercial or non-commercial software that you distribute provided that the Default CDEF is not modified in any way and that Lim Unlimited is given a free, fully functional, and fully supported copy of your software. You are not required to include a copyright notice for Default in your software.
The source code to Default is now available on request. You can get a copy via electronic mail or by sending a stamped self-addressed envelope and a disk via postal mail. The source code may not be redistributed without the permission of Lim Unlimited. You may not distribute modified versions of the source code or any software derived from the source code.
Lim Unlimited
Postal: 330 W. Iris, Stockton, CA 95210, U.S.A.
Internet: lim@iris.ucdavis.edu
America Online: LimUnltd
CompuServe: 72647,660
/*
Default is a CDEF written using THINK C. This CDEF replaces the standard CDEF 0 and
simply draws a default button outline and calls the standard CDEF 0 to handle
everything else.
(c) Lim Unlimited, 9 Jul 1990 - All Rights Reserved
*/
/*
header files
*/
#include <Color.h>
#include <ColorToolbox.h>
#include <FontMgr.h>
/*
constants and macros
*/
#define RESEDIT 1 /* whether to compile ResEdit updating code */
#define NIL ((void *) 0)
#define INACTIVE 255
#define STANDARD_CDEF_ID 0
enum { /* new 32-bit clean messages */
calcCntlRgn = 10,
calcThumbRgn
};
#define LO_3_BYTES 0x00FFFFFF
#define SYS_ENVIRONS_VERSION 1
#define DEFAULT_FLAG '@'
#define USEWFONT_FLAG '[[florin]]'
#define OUTLINE_THICKNESS 3
#define OUTLINE_INSET (OUTLINE_THICKNESS + 1)
#define OUTLINE_OUTSET (-OUTLINE_INSET)
#define CURVE_ADJUSTMENT 2
#define RECT_IN_RECT(r1, r2) \
((r1)->top >= (r2)->top && (r1)->left >= (r2)->left && \
(r1)->bottom <= (r2)->bottom && (r1)->right <= (r2)->right)
/*
typedefs
*/
typedef struct {
ControlHandle itmHndl;
Rect itmRect;
Byte itmType;
Byte itmData[1];
} Item, *ItemPtr;
typedef struct {
short dlgMaxIndex;
Item item[1];
} ItemList, *ItemListPtr, **ItemListHdl;
typedef struct {
Handle standardCDEF;
unsigned has128KROMS:1;
unsigned hasColorQD:1;
unsigned dialogButton:1;
unsigned dialogDefault:1;
#if RESEDIT
unsigned inResEdit:1;
#endif
} DefaultData, *DefaultDataPtr, **DefaultDataHdl;
/*
routines
*/
PASCAL long main (short, ControlHandle, short, long);
/*
static routines
*/
static long CallStandardCDEF (short, ControlHandle, short, long);
static Boolean RealHandle (long, Boolean);
static short FindItemNum (DialogPeek, ControlHandle);
/*
static variables
*/
static unsigned char Copyright[] = "Default 2.1, (c)1990 Lim Unlimited";
/*
main draws the default button outline and calls the standard button CDEF.
*/
PASCAL long main(varCode, theControl, message, param)
register short varCode;
register ControlHandle theControl;
register short message;
register long param;
{
register DefaultDataHdl defaultDataHdl;
unsigned char *title;
DialogPeek theDialog;
Boolean isDefault, useWindowFont, dialogDefault;
short oldRefNum;
Handle standardCDEF;
SysEnvRec sysEnvirons;
Rect button;
AuxCtlHndl auxCtlHdl;
RGBColor oldColor, frameColor;
PenState penState;
Pattern gray;
RgnHandle outlineRgn;
register short curve;
register long result;
#if RESEDIT
RgnHandle visRgn, oldVisRgn;
Rect visButton, visBounds;
#endif
title = (*theControl)->contrlTitle;
if (!(varCode & ~useWFont) && title[title[0]] == (unsigned char) DEFAULT_FLAG) {
isDefault = TRUE;
--title[0];
} else {
isDefault = FALSE;
}
if (useWindowFont = (title[title[0]] == (unsigned char) USEWFONT_FLAG)) {
varCode |= useWFont;
--title[0];
}
theDialog = (DialogPeek) (*theControl)->
;
if (message == initCntl) {
defaultDataHdl = (DefaultDataHdl) NewHandle(sizeof(DefaultData));
(*theControl)->contrlData = (Handle) defaultDataHdl;
oldRefNum = CurResFile();
UseResFile(0);
standardCDEF = GetResource('CDEF', STANDARD_CDEF_ID);
(*defaultDataHdl)->standardCDEF = standardCDEF;
UseResFile(oldRefNum);
HNoPurge(standardCDEF);
result = SysEnvirons(SYS_ENVIRONS_VERSION, &sysEnvirons);
(*defaultDataHdl)->has128KROMS = (sysEnvirons.machineType >= envMachUnknown);
(*defaultDataHdl)->hasColorQD = sysEnvirons.hasColorQD;
if (!(varCode & ~useWFont) &&
RealHandle((long) theDialog->items, !result) &&
RealHandle((long) theDialog->textH, !result) &&
theDialog->editField >= -1 &&
theDialog->editField <= (*(ItemListHdl) theDialog->items)->dlgMaxIndex) {
(*defaultDataHdl)->dialogButton = TRUE;
} else {
(*defaultDataHdl)->dialogButton = FALSE;
}
(*defaultDataHdl)->dialogDefault = FALSE;
#if RESEDIT
/* ResEdit is being used if the control's window has no controls attached */
if (!theDialog->window.controlList && theDialog == (DialogPeek) FrontWindow()) {
(*defaultDataHdl)->inResEdit = TRUE;
} else {
(*defaultDataHdl)->inResEdit = FALSE;
}
#endif
result = CallStandardCDEF(varCode, theControl, message, param);
} else {
defaultDataHdl = (DefaultDataHdl) (*theControl)->contrlData;
/* set default button item number for Dialog Manager */
if ((*defaultDataHdl)->dialogButton) {
dialogDefault = (isDefault && !(*theControl)->contrlHilite);
if (dialogDefault && !(*defaultDataHdl)->dialogDefault) {
theDialog->aDefItem = FindItemNum(theDialog, theControl);
if (theDialog->aDefItem) {
(*defaultDataHdl)->dialogDefault = TRUE;
}
} else if (!dialogDefault && (*defaultDataHdl)->dialogDefault) {
if (theDialog->aDefItem == FindItemNum(theDialog, theControl)) {
theDialog->aDefItem = 0;
}
(*defaultDataHdl)->dialogDefault = FALSE;
}
}
if (message == drawCntl) {
result = CallStandardCDEF(varCode, theControl, message, param);
if (isDefault && (*theControl)->contrlVis) {
#if RESEDIT
/* portions of the ResEdit window are invalidated under certain
circumstances so it will update correctly */
if ((*defaultDataHdl)->inResEdit) {
visRgn = theDialog->window.port.visRgn;
visBounds = (*visRgn)->rgnBBox;
button = (*theControl)->contrlRect;
if (SectRect(&button, &theDialog->window.port.portRect, &visButton) &&
RECT_IN_RECT(&visButton, &visBounds)) {
InsetRect(&button, OUTLINE_OUTSET, OUTLINE_OUTSET);
if (SectRect(&button, &theDialog->window.port.portRect, &visButton) &&
!RECT_IN_RECT(&visButton, &visBounds)) {
outlineRgn = NewRgn();
oldVisRgn = NewRgn();
CopyRgn(visRgn, outlineRgn);
CopyRgn(visRgn, oldVisRgn);
RectRgn(visRgn, &theDialog->window.port.portRect);
InsetRgn(outlineRgn, OUTLINE_OUTSET, OUTLINE_OUTSET);
DiffRgn(outlineRgn, oldVisRgn, outlineRgn);
EraseRgn(outlineRgn);
CopyRgn(oldVisRgn, visRgn);
DisposeRgn(oldVisRgn);
InvalRgn(outlineRgn);
DisposeRgn(outlineRgn);
}
}
}
#endif
if ((*defaultDataHdl)->hasColorQD) {
(void) GetAuxCtl(theControl, &auxCtlHdl);
GetForeColor(&oldColor);
if (auxCtlHdl) {
frameColor = (*(*auxCtlHdl)->acCTable)->ctTable[cFrameColor].rgb;
RGBForeColor(&frameColor);
}
}
GetPenState(&penState);
PenNormal();
PenSize(OUTLINE_THICKNESS, OUTLINE_THICKNESS);
if ((*theControl)->contrlHilite == INACTIVE) {
*(unsigned long *) &gray[0] = *(unsigned long *) &gray[4] = 0xAA55AA55;
PenPat(&gray);
}
button = (*theControl)->contrlRect;
InsetRect(&button, OUTLINE_OUTSET, OUTLINE_OUTSET);
curve = ((button.bottom - button.top) >> 1) + CURVE_ADJUSTMENT;
FrameRoundRect(&button, curve, curve);
SetPenState(&penState);
if ((*defaultDataHdl)->hasColorQD) {
RGBForeColor(&oldColor);
}
}
} else if (message == calcCRgns ||
message == calcCntlRgn ||
message == calcThumbRgn) {
result = CallStandardCDEF(varCode, theControl, message, param);
if (isDefault && ((message == calcCRgns && param > 0) ||
message == calcCntlRgn)) {
OpenRgn();
button = (*theControl)->contrlRect;
InsetRect(&button, OUTLINE_OUTSET, OUTLINE_OUTSET);
curve = ((button.bottom - button.top) >> 1) + CURVE_ADJUSTMENT;
FrameRoundRect(&button, curve, curve);
CloseRgn((RgnHandle) param);
}
} else if (message == dispCntl) {
result = CallStandardCDEF(varCode, theControl, message, param);
/* MultiFinder loads and shares system resources in the system heap; make
standard button CDEF purgeable only if it is in the application heap */
standardCDEF = (*defaultDataHdl)->standardCDEF;
if (HandleZone(standardCDEF) == ApplicZone()) {
HPurge(standardCDEF);
}
DisposHandle(defaultDataHdl);
#if 0
/* messages not used by Default but documented for completeness */
} else if (message == testCntl ||
message == posCntl ||
message == thumbCntl ||
message == dragCntl ||
message == autoTrack) {
result = CallStandardCDEF(varCode, theControl, message, param);
#endif
/* pass along messages which are not used by Default or are currently undefined */
} else {
result = CallStandardCDEF(varCode, theControl, message, param);
}
}
if (useWindowFont) {
++(*theControl)->contrlTitle[0];
}
if (isDefault) {
++(*theControl)->contrlTitle[0];
}
return(result);
}
/*
CallStandardCDEF calls the standard button CDEF.
*/
static long CallStandardCDEF(varCode, theControl, message, param)
short varCode;
ControlHandle theControl;
short message;
long param;
{
register DefaultDataHdl defaultDataHdl;
register Handle standardCDEF;
register SignedByte flags;
register long result;
defaultDataHdl = (DefaultDataHdl) (*theControl)->contrlData;
/* load standard button CDEF if it was purged */
standardCDEF = (*defaultDataHdl)->standardCDEF;
if (!*standardCDEF) {
LoadResource(standardCDEF);
HNoPurge(standardCDEF);
}
/* save and restore handle state just in case control is reentrant */
if ((*defaultDataHdl)->has128KROMS) {
flags = HGetState(standardCDEF);
} else {
flags = (long) *standardCDEF >> 24;
}
HLock(standardCDEF);
result = CallPascalL(varCode, theControl, message, param, *standardCDEF);
if ((*defaultDataHdl)->has128KROMS) {
HSetState(standardCDEF, flags);
} else {
*standardCDEF = (Ptr) (((long) *standardCDEF & LO_3_BYTES) | (flags << 24));
}
return(result);
}
/*
RealHandle returns whether the given address is a handle in the system heap or
application heap.
*/
static Boolean RealHandle(addr, hasStripAddr)
register long addr;
Boolean hasStripAddr;
{
register Boolean real;
register THz sysZone, applZone, heapZone;
real = FALSE;
addr = (hasStripAddr) ? StripAddress(addr) : addr & LO_3_BYTES;
if (addr && !(addr & 1)) {
sysZone = SystemZone();
applZone = ApplicZone();
if (((addr >= (long) &sysZone->heapData && addr < (long) sysZone->bkLim) ||
(addr >= (long) &applZone->heapData && addr < (long) applZone->bkLim)) &&
*(long *) addr && !(*(long *) addr & 1)) {
heapZone = HandleZone(addr);
if (!MemError() && (heapZone == sysZone || heapZone == applZone)) {
real = TRUE;
}
}
}
return(real);
}
/*
FindItemNum returns the item number of the given control in the given dialog.
*/
static short FindItemNum(theDialog, theControl)
DialogPeek theDialog;
ControlHandle theControl;
{
register short numItems, itemNum;
register Ptr itemPtr;
numItems = (*(ItemListHdl) theDialog->items)->dlgMaxIndex + 1;
itemPtr = (Ptr) (*(ItemListHdl) theDialog->items)->item;
for (itemNum = OK; itemNum <= numItems; ++itemNum) {
if (((ItemPtr) itemPtr)->itmHndl == theControl) return(itemNum);
itemPtr += sizeof(Item) + (((long) ((ItemPtr) itemPtr)->itmData[0] + 1) & ~1);
}
return(0);
}
While developing Disinfectant I ran into a number of "gotchas" that caused me great grief. I thought it would be nice to tell the rest of you about these problems, in the hope that you'll be able to avoid them in your own programs. I've told DTS about most of this stuff.
curZone = GetZone(); SetZone(HandleZone(theRez)); ReleaseResource(theRez); SetZone(curZone);
I hope my experiences help somebody.
John Norstad Academic Computing and Network Services Northwestern University
The code I posted yesterday would cause problems in the unlikelyevent that your program would crash while the Menu Bar was hidden. Specifically, it would replace GrayRgn with a handle to a Region in the application heap, and save the real GrayRgn Handle for restoration later when you restored the Menu Bar. If your program crashed, and the Menu Bar was hidden, then GrayRgn was left pointing to a Region in a defunct application heap, which could cause all sorts of problems for applications which were still running.
This version does not change the Handle, but rather modifies the contents of GrayRgn. If you crash with the Menu Bar hidden, GrayRgn is left pointing to a valid area of storage, at least. There are still problems since the Menu Bar can be left hidden, but these are slightly less severe than leaving a dangling Handle in the system heap.
Earle
{
This unit provides the ability to hide the Menu Bar, and to show
it. When the Menu Bar is hidden, windows may be placed in the
area normally used for the Menu Bar.
Usage:
PROCEDURE MBar_Init:
Used to initialize global variables used here.
Call once at beginning of application code.
PROCEDURE MBar_be_Gone;
Hides the Menu Bar.
PROCEDURE MBar_Restore;
Shows the Menu Bar. Call this whenever you are going
to be put into the background. Call before Exit.
The procedures in this unit will probably break under some future
release of the Macintosh Operating System, because they manipulate
the GrayRgn. They do work under MultiFinder 6.1a2. The most
serious warning I can give concerning use of these routines is
that you must never allow your application to be placed into the
background with the Menu Bar hidden.
Possible compatibility problems:
Modifies lowmem MBarHeight
Modifies contents of GrayRgn
This file is part of Earle R. Horton's private source code library.
Earle R. Horton assumes no responsibility for any damages arising
out of use of this source code for any purpose. Earle R. Horton
places no restrictions on use of all or any part of this source code,
except that this paragraph may not be altered or removed.
Original Language:
MPW Pascal, v. 2.0.2
Origination Date:
March 20, 1989
Modifications:
April 4, 1989 ERH
First version changed lowmem GrayRgn to point to a Region in the
application heap while the Menu Bar was hidden. This version copies the
new Region to GrayRgn. If the application crashes with the Menu Bar
hidden, GrayRgn no longer points to part of the defunct application
heap. There are still problems, however, because the Menu Bar is left
hidden and other applications cannot access it.
}
UNIT MenuBar;
INTERFACE
USES
{$Load PasDump.dump}
Memtypes, Quickdraw, OSIntf, Script, ToolIntf;
PROCEDURE MBar_be_Gone;
PROCEDURE MBar_Restore;
PROCEDURE MBar_Init;
PROCEDURE SetMBarHeight(newheight:integer);
INLINE smPopStack2Word,smMBarHeight; { move.w (a7)+,$0BAA }
VAR
Real_MBar_Height: integer; { Copy of lowmem MBarHeight }
Save_Region: RgnHandle; { Copy of GrayRgn }
Hidden_Flag: Boolean; { State info }
MBar_Rect: Rect; { Rect in which MBar is drawn }
IMPLEMENTATION
PROCEDURE MBar_Init;
BEGIN
Hidden_FLag := false;
Real_MBar_Height := GetMBarHeight;
SetRect(MBar_Rect,
screenBits.bounds.left,
screenBits.bounds.top,
screenBits.bounds.right,
screenBits.bounds.top + Real_MBar_Height);
END;
{
Make the Menu Bar go away under MultiFinder or UniFinder.
Since this procedure manipulates the Gray Region, future
compatibility is unknown.
}
PROCEDURE MBar_be_Gone;
VAR
MBar_Region: RgnHandle;
theWindow: WindowPeek;
BEGIN
IF not Hidden_Flag THEN
BEGIN
Hidden_Flag := true;
{ Get some Regions to work with}
Save_Region := NewRgn;
MBar_Region := NewRgn;
{ Set the Menu Bar height to zero}
SetMBarHeight(0);
}
CopyRgn(GetGrayRgn,Save_Region);
{ Fix up GrayRgn to cover the old GrayRgn plus the Menu Bar Rect}
RectRgn(MBar_Region,MBar_Rect);
UnionRgn(GetGrayRgn,MBar_Region,GetGrayRgn);
{ Paint and fix up visRgn for any windows with exposed area}
theWindow := WindowPeek(FrontWindow);
PaintOne(theWindow,MBar_Region);
PaintBehind(theWindow,MBar_Region);
CalcVis(theWindow);
CalcVisBehind(theWindow,MBar_Region);
DisposeRgn(MBar_Region); { Clean up, leave}
END;
END;
{
Restore the Menu Bar and GrayRgn to normality.
Call when app is put into background.
}
PROCEDURE MBar_Restore;
VAR
theWindow: WindowPeek;
BEGIN
IF Hidden_Flag THEN
BEGIN
Hidden_Flag := false;
{ Restore to original }
CopyRgn(Save_Region,GetGrayRgn);
{ Restore Menu Bar height }
SetMBarHeight(Real_MBar_Height);
{ Fix up any covered windows }
RectRgn(Save_Region,MBar_Rect);
theWindow := WindowPeek(FrontWindow);
CalcVis(theWindow);
CalcVisBehind(theWindow,Save_Region);
DisposeRgn(Save_Region);
{ Draw the Menu, get out of here }
HiliteMenu(0);
DrawMenuBar;
END;
END;
END.