Chapter 15 Articles & Notes


  1. Macintosh One-Liners
    1. The Main Loop and Events
    2. Menus
    3. Resources
    4. Windows, Alerts, and Dialogs
    5. Drawing
    6. Interrupts and VBL Tasks
    7. Files
    8. Handles and Pointers
    9. General
    10. Controversy Corner (Don't shoot me; I'm just the messenger.)
  2. Scheme to Manage a "Windows" menu
  3. How to write an INIT in Pascal
  4. How do you play Asynchronous Sound?
  5. Default 2.1 CDEF
  6. ToolBox Gotchas
  7. BitMap Rotation in C (and support routines)
  8. INIT Skeleton Code
  9. New Volume Scanning Algorithm

Macintosh One-Liners

by Eric Pepke

The Macintosh One-Liners are intended to condense into a small space information about some of the most common Macintosh problems and programming pitfalls. Each one-liner is a single line of text, shorter than 80 characters, which informs about one aspect of Macintosh use or programming.

One-liners give either facts or advice. The facts may be obvious to some people and obscure to others but are important for all. The advice is intended to help keep people from running into the most common nontrivial problems. Like proverbs, the advice may not be absolute and may sometimes be more conservative than is strictly necessary. However, I have found that a little constructive paranoia can go a long way toward avoiding problems, and more than once I have taken a precaution which seemed extreme at the time but which saved my skin later on.

The one-liners started as a list I made for myself of things to remember while writing programs. I have augmented them with my condensed records of several years of Info-Mac, Usenet, and Delphi digests and one year of Usenet reading. People who have contributed to the list since its first release are mentioned at the end. The result is very much a gestalt of the Macintosh lore I have seen and depends on the wisdom and efforts of many people. If I have forgotten to include your name, I apologize.

The one-liners are organized into two sections. The first lists the one-liners under broad categories without explanation. This is good for cutting out and sticking on the wall. The second section gives some extra information on each one-liner.

All references given are to Apple documentation. In some cases, the references explain the problem, but in most, they just describe the parts of the Toolbox you need to use. Most of the problem fixes are a result of my experience and the experience of those people who are listed below. The acronym IM means Inside Macintosh; the page numbers refer to the Addison-Wesley trade paperback version. TN means Technical Note. I use the HyperCard stack, available from sumex-aim.stanford.edu, apple.com, and no doubt other places as well. I have avoided referring to any third-party software tools and only refer to a minimal set of Apple tools. This is not to endorse Apple in any way. It's just that I don't want to get into the product comparison business. I use a minimal set of ubiquitous tools, and I describe fixes in terms of those tools. If you have different tools, you can translate. I have also tried to avoid using language-specific statements where possible, but all my example code is in C. Again, you can translate.

Compiled by Eric Pepke

Additional material by Steve Maker, Keith Rollin, Gregory Dudek, Brian Bechtel,

Henry Minsky, Carl C. Hewitt, Jim Lyons, Alex Lau, Kent Borg, Peter W. Poorman,

Ross Yahnke, Mark Fleming, Mark Anderson.

Send suggestions to pepke@gw.scri.fsu.edu on the

Internet or PEPKE@FSU on BITNET. Have fun.


The Main Loop and Events

  • Call MaxApplZone and MoreMasters when the application starts up.
  • If you call SetApplLimit, do it before calling MaxApplZone.
  • Use HT in MacsBug while running to estimate how many times to call MoreMasters.
  • Don't use SetEventMask to disable mouseUp events. Better not to use it at all.
  • SetPort to a known good GrafPort once every time through the event loop.
  • Calling WaitNextEvent with more than 50 ticks will fail on some systems.
  • Set the cursor on suspend and resume events.
  • Call GetDblTime to get the maximum time for a double click.
  • Measure double-click time from mouse up to mouse down.
  • Call either WaitNextEvent or both GetNextEvent and SystemTask.
  • Call IsDialogEvents and DialogSelect even if GetNextEvent returns false.

    Menus

  • Use SetItem to include meta characters literally in menus.
  • Never make MENU resources purgeable.


    Resources

  • GetResource never produces resNotFound. Check for a NIL handle instead.
  • To create a resource file, call Create, then CreateResFile.
  • To open a resource file read-only for shared access, use OpenRFPerm.
  • Don't leave ResLoad set to false.
  • GetResource on a dctb may return a non-resource copy of the dctb.

    Windows, Alerts, and Dialogs

  • Drag windows to the bounding box of GetGrayRgn.
  • Hide scroll bars when deactivating a window.
  • Call DrawGrowIcon when activating or deactivating a window with a grow region.
  • DrawGrowIcon does not check to see if the window has a grow region.
  • Call PenNormal before calling DrawGrowIcon.
  • ItemHit will not be set when a dialog filter is called.
  • Use a disabled UserItem to draw the roundrect outline around the OK button.
  • ModalDialog assumes the dialog is already visible and in the front.
  • Use screenBits.bounds to center dialogs, alerts, etc. below the menu bar.
  • If you save window locations in files, they may not be valid for all monitors.
  • DragWindow expects startPt in boundsRect; if not it may not call SelectWindow.
  • SelectWindow does not automatically call SetPort. You must do that yourself.
  • DialogSelect responds to activate events but ignores suspend/resume events.
  • Call PenNormal before calling DrawControls.
  • The Control Manager only works when the origin of a window is at (0, 0)
  • To find the position of a window, use LocalToGlobal on the origin.

    Drawing

  • Always set the VisRgn and ClipRgn of offscreen ports.
  • Set the ClipRgn first when making a picture.
  • Don't make rowBytes in bitMaps greater than 8191.
  • To dim text, draw a rectangle with penPat=gray and penMode=patBic over it.
  • To draw rotated text, draw to an offscreen bitmap, rotate it, and CopyBits it.
  • Don't use picSize to determine the size of a picture. Check the handle size.
  • Never draw outside a window except in XOR mode for temporary effects like drag.
  • To avoid animation flicker, synchronize drawing to the vertical retrace.
  • Lock handles to pictures before calling DrawPicture.
  • CopyBits on more than 3Kb data will work, but it might have to allocate memory.
  • The small Mac screen is 512 pixels wide by 342 pixels high including menu bar.

    Interrupts and VBL Tasks

  • Don't call any Memory Manager routines during an interrupt.
  • Unlocked handles may not be valid during an interrupt.
  • To synchronize to the vertical retrace on Macs with slots, use SlotVInstall.

    Files

  • Don't write in the application file. This will fail with read-only devices.
  • Use PBGetVInfo to convert a VRefNum to a volume name.
  • Delete uses the Poor Man's Search Path, so don't delete blindly.
  • File Manager routines with dirID=0 use the Poor Man's Search Path.
  • Truncate and reallocate files before overwriting to reduce fragmentation.
  • Check/change the creator and type of Save As... files before overwriting.
  • If you rewrite files by deleting and creating, copy all Finder information.
  • Directory ID's are longs, not shorts. Shorts work ALMOST all the time.
  • If a file version number appears in a file manager call, always set it to 0.
  • To convert a pathRefNum to a name or file number, use PBGetFCBInfo.
  • Prevent the creation of files with names that begin with a period.
  • Write/update the Finder info before writing data or resource forks.

    Handles and Pointers

  • Lock handles before passing their dereferenced pointers to any routine.
  • Lock handles before setting referenced data to expressions containing functions
  • Put an odd long at location zero on a 68000 to help find NIL handle references.
  • Call MoveHHi before locking a handle to avoid memory fragmentation.
  • Use HGetState/HLock/HSetState to lock a handle then put it back as it was.

    General

  • Always use unsigned characters within text and Pascal-format strings.
  • Save application preferences in a folder named Preferences in the System Folder
  • Use SysEnvirons to find the System (Blessed) Folder.
  • Use GetAppParms to get the name of the application.
  • The high bit of SysParam . volClik enables the alarm clock.
  • Check the application name at $910 before exiting with ES within MacsBug.
  • To exit to shell in the mini-debugger, enter SM 0 A9 F4 and then G 0.
  • In Pascal, don't nest procedures to be passed by procedure pointer.
  • In Pascal, "with theHandle^^" is unsafe if memory compaction can occur.

    Controversy Corner (Don't shoot me; I'm just the messenger.)

  • Avoid writing tail patches for traps.
  • There is no official way to tell if MultiFinder is running or not.

    [This section was created by Eric after my futile attempt at trying to explain his one-liners. I showed him my idea and he was real interested and expanded on it. The idea was that his one-liners was very helpful at remembering the correct things to do but to help some beginning programmers , I thought that an explanation would help them understand why. Also I added a few comments and they are shown by square brackets. MXM ]
    ---

    Call MaxApplZone and MoreMasters when the application starts up.

    References: IM II-30, IM II-31, TN 53

    A call to MaxApplZone is needed for an application to get as large a heap as it can under all systems. Usually, this is what you want to do. Call MoreMasters early in the application to get enough blocks of master pointers for the anticipated number of relocatable memory blocks to be allocated. [You should also call MoreMasters from code segment 1 and not from your intialization segment.]

    If you call SetApplLimit, do it before calling MaxApplZone.

    References: IM II-30

    MaxApplZone expands the application zone to the heap limit set by SetApplLimit. It doesn't make any sense to call MaxApplZone first. Most applications will not need SetApplLimit at all, but the few that do should call it before MaxApplZone.

    Use HT in MacsBug while running to estimate how many times to call MoreMasters.

    References: IM II-22, IM II-31, MacsBug 6.0, TN 53

    There is an art to esimating how many times to call MoreMasters. If you call it too few times, the Memory Manager will be forced to call it later on in the application, which can fragment the heap. If you call it too many times, you will be wasting master pointers that you do not need. It's usually better to call MoreMasters too many times than too few. To estimate the number of times, you need to have some idea of how many relocatable blocks your application typically allocates. A good way of finding out is to run the application for a while, exercising all its features. Then find out how many relocatable memory blocks have been allocated. The HT command in MacsBug will provide this information. Once you have this number, divide by 64, add some slop (maybe 25%), and that's the number of times you need to call MoreMasters.

    Don't use SetEventMask to disable mouseUp events. Better not to use it at all.

    References: IM II-70, TN 202

    SetEventMask should only be used to enable KeyUp events if an application needs them. Some programmers, especially in the early days, used SetEventMask to disable MouseUp events, which caused a lot of problems. One particularly strange one is that, after running them, double-clicking would no longer work in the Finder. Because the Finder could not see the MouseUp, it could not detect a double-click.

    SetPort to a known good GrafPort once every time through the event loop.

    References: IM I-165

    You never know if a desk accessory is playing by the rules. Even if you do things absolutely correctly in your application, there is a bizarre set of circumstances which can (and has) resulted in crashes. Say desk accessory A plays hard and fast with the current GrafPort. Say it has the current GrafPort set to a window, and it closes the window. The current GrafPort becomes invalid. Now, desk accessory B does something which requires the current GrafPort to be valid, but does not explicitly set it first. This is common because there are calls that expect some GrafPort to be current, but don't really care which one, and some programmers are sloppy. When B tries this, kabloom! Even though your application is perfectly innocent, it will probably be blamed anyway. If you do a SetPort to a known good GrafPort every time through your event loop, this will not happen.

    Calling WaitNextEvent with more than 50 ticks will fail on some systems.

    References: TN 177

    MultiFinder 1.0 had a bug which caused it to hang if you called WaitNextEvent from the background with more than about 50 ticks. The bug has since been fixed, but you never know what weird system will be used to run your application.

    Set the cursor on suspend and resume events.

    References: IM I-167, Programmer's Guide to MultiFinder

    When you receive a resume event, another application may have changed the cursor, so set it to whatever you need it to be, even if it is just an arrow. When you receive a suspend event, set the cursor to an arrow as a courtesy to other applications whose programmers are not as well informed as you.

    Call GetDblTime to get the maximum time for a double click.

    References: IM I-261

    The double-click time can be set by the user via the Control Panel. Use this value to determine whether two sequential clicks formed a double click. People have different capabilities and natural speeds, and some users may be physically unable to make a double click at a certain speed. Also remember that the value of GetDblTime can change at any time, because the user can pull down the Control Panel at any time.

    Measure double-click time from mouse up to mouse down.

    The Finder measures a double click from a MouseUp event to a MouseDown event. This seems to be a very natural way of doing it.

    Call either WaitNextEvent or both GetNextEvent and SystemTask.

    References: Programmer's Guide to Multifinder

    WaitNextEvent is not just a direct replacement for GetNextEvent. It performs the function of SystemTask as well.

    [ Example Below is from Apple's TESample Source Code ]
    procedure EventLoop;
    
    {Get events forever, and handle them by calling DoEvent.}
    { Also call AdjustCursor each time through the loop.}
    
    		var
    			cursorRgn: RgnHandle;
    			gotEvent: BOOLEAN;
    			event: EventRecord;
    			mouse: Point;
    
    	begin
    		cursorRgn := NewRgn;		{we'll pass an empty region to WNE the first time thru}
    		repeat
    			if gHasWaitNextEvent then
    				begin	{put us 'asleep' forever under MultiFinder}
    					GetGlobalMouse(mouse);		{since we might go to sleep}
    					AdjustCursor(mouse, cursorRgn);
    					gotEvent := WaitNextEvent(everyEvent, event, GetSleep, cursorRgn);
    				end
    			else
    				begin
    					SystemTask;			{must be called if using GetNextEvent}
    					gotEvent := GetNextEvent(everyEvent, event);
    				end;
    			if gotEvent then
    				begin
    					AdjustCursor(event.where, cursorRgn); {make sure we have the right cursor}
    					DoEvent(event);				{Handle the event only if for us.}
    				end
    			else
    				DoIdle;
    		{If you are using modeless dialogs that have editText items,}
    		{you will want to call IsDialogEvent to give the caret a chance}
    		{to blink, even if WNE/GNE returned FALSE. However, check FrontWindow}
    		{for a non-NIL value before calling IsDialogEvent.}
    		until AllDone
    	end; {EventLoop}
    
    

    Call IsDialogEvents and DialogSelect even if GetNextEvent returns false.

    References: IM I-416

    In addition to handling dialog events, these routines also control cursor flashing in dialog EditText items. They need to be called even as a result of null events to make sure the flashing occurs.

    Use SetItem to include meta characters literally in menus.

    References: IM I-346, IM I-357

    AppendMenu recognizes certain characters as meta characters to control the text style, keyboard equivalents, and so on. If you want to display any of these meta characters within the text in the menu, you will need to use SetItem instead.

    Never make MENU resources purgeable.

    GetResource never produces resNotFound. Check for a NIL handle instead.

    References: IM I-119

    [ quick example ]

    	id:=1000;
    	myResHandle:=GetResource('ICON', id);
    	if myResHandle<>nil then 
    		PlotIcon(r, myResHandle);
    	else
    		SysBeep(duration);
    
    

    To create a resource file, call Create, then CreateResFile.

    References: IM I-114, TN 101

    CreateResFile will first check to see if the file exists, and if it does, it will add a resource fork to that file. CreateResFile uses the Poor Man's Search Path, so if you call it by itself, you may find that it actually modifies a file in the system folder rather than creating a file where you want it. To avoid this, call Create first to make sure a file exists where you want it.

    To open a resource file read-only for shared access, use OpenRFPerm.

    References: IM IV-17, TN 116, TN 185

    Don't leave ResLoad set to false.

    References: IM I-118

    The SetResLoad routine should only be used in very special circumstances to prevent the automatic loading of resources into memory. Parts of the Toolbox require ResLoad to be true, so ResLoad should only be false for a very short time.

    GetResource on a dctb may return a non-resource copy of the dctb.

    When GetResource is called on a dctb, it may return a copy of the dctb rather than a handle to the resouce itself. This was done as a kludge to fix a bizarre problem. Of course, if you aren't aware of it, the kludge itself can cause some bizarre problems in programs that try to edit dctb resources.

    Drag windows to the bounding box of GetGrayRgn.

    References: IM I-282, IM I-289, IM V-208

    DragWindow requires a bounds rectangle to specify the area over which the window can be dragged. Normally, it should be possible to drag a window anywhere. Once upon a time applications were recommended to use screenBits.bounds as the drag rectangle. Many pieces of Apple sample code recommend this to this day. However, on systems with multiple monitors, screenBits.bounds only enclose the screen with the menu bar. What you really want is to be able to drag the window within a rectangle that encloses all screens. The best one to use is the bounding box of the return value of GetGrayRgn. Because the GrayRgn covers all monitors, the bounding box is at least large enough. If the system is too old to have the GetGrayRgn function, you can use the GrayRgn global variable or just assume that screenBits.bounds is good enough. The following sample THINK C code drags theWindow around in response to an EventPtr called theEvent:
        Rect boundsRect;
        boundsRect = (*GetGrayRgn()) -> rgnBBox;
        DragWindow(theWindow, theEvent -> where, &boundsRect);
    

    Hide scroll bars when deactivating a window.

    References: IM I-46

    The entire scroll bar of an inactive window, including the arrows at the top and bottom, is supposed to be hidden. Few applications pay attention to this, but it is one of those little touches that really cleans up the feel of a program.

    Call DrawGrowIcon when activating or deactivating a window with a grow region.
    DrawGrowIcon does not check to see if the window has a grow region.
    Call PenNormal before calling DrawGrowIcon.

    References: IM I-287

    DrawGrowIcon is not smart enough to know if the window has a grow region, so don't assume that you can always call it in a generic window handler. On the other hand, it is smart enough to know if the window is active or inactive, so it always does the right thing. It assumes certain things about the QuickDraw environment, such as a pen size of (1, 1), so it is safest to call PenNormal before every call to DrawGrowIcon.

    ItemHit will not be set when a dialog filter is called.

    References: IM I-415, TN 112

    This is one of the trickiest pitfalls to using dialogs. Dialog filters are nearly always used to provide interactions in userItems and are specific to those userItems. Thinking about that task, it is reasonable to assume that the filter can just test itemHit to see if the action is appropriate to the userItem. Though reasonable, the assumption is wrong. A dialog filter gets the raw event before any decisions are made. It is entirely responsible for determining where the click was and setting itemHit. An easy way to do this for a userItem is to do a PtInRect with the rectangle around the userItem. You can also use FindDItem.

    [ Sample below is from an old program that had a list manager in the dialog MXM].
    function MyFilter (theDialog: DialogPtr; var theEvent: EventRecord; var itemHit: integer): Boolean;
    
    		var
    			key: SignedByte;
    			Itype: Integer;
    			iBox, r: Rect;
    			iHdl: Handle;
    			MouseLoc: Point;
    			isDbl: boolean;
    			myList: ListHandle;
    	begin
    		Setport(theDialog);
    		MyFilter := False;
    		MyList := ListHandle(GetWRefCon(theDialog));
    		MyList^^.port^.txSize := 9;
    		MyList^^.port^.txfont := 4;
    		case theEvent.what of
    			keyDown, autoKey: 
    				begin
    					key := theEvent.message;
    					if key in [13, 3] then
    						begin
    							MyFilter := true;
    							itemHit := 9;		{Set Itemhit}
    						end;
    				end;
    			mouseDown: 
    				begin
    					mouseLoc := theEvent.where;
    					GlobalToLocal(mouseLoc);
    					GetDItem(theDialog, 3, iType, ihdl, ibox);
    					r := Ibox;
    					r.right := r.right + 16;
    					if PtInRect(mouseLoc, r) then
    						begin
    							isDbl := LClick(mouseLoc, theEvent.modifiers, Mylist);
    							MyFilter := true;
    							itemHit := 3;
    						end;
    				end;
    			UpdateEvt: 
    				begin
    					BeginUpdate(theDialog);
    					LUpdate(theDialog^.visrgn, Mylist);
    					MyList^^.port^.txSize := 9;
    					MyList^^.port^.txfont := 4;
    					DrawDialog(theDialog);
    					EndUpdate(theDialog);
    				end;
    		end;
    	end;
    
    

    Use a disabled UserItem to draw the roundrect outline around the OK button.

    References: IM I-421, TN 34, default cdev

    It seems strange that there is no trivial way to do such a basic feature of the Macintosh interface, but that's the truth. How does one make the roundrect outline around the default button that we all love so much? Just drawing it there after showing the dialog is not good enough, because if something happens to cover up the dialog, the outline will be erased. Some people use disabled PICT items containing roundrects, but you really have to make a custom PICT for every size of button to get the thickness of the line just right. A better way is to use a userItem that draws the outline. Add to the dialog a disabled userItem that is larger than the OK button by 4 pixels on each side. Make the dialog hidden, so that you will have to do a ShowWindow to show it. Then install a procedure for the userItem which draws the outline within its rectangle. Here are some THINK C code fragments:

    pascal void DrawOKOutline(theDialog, whichItem)
    DialogPtr theDialog;
    INTEGER whichItem;
    /*Draws an OK outline in a userItem*/
    {
        INTEGER itemType;	/*The type of the item in GetDItem*/
        Handle theItem;	/*The handle to the item*/
        Rect itemBox;	/*The box containing the item*/
        PenState savedPenState;	/*The old pen state saved and later restored*/
    	
        GetDItem(theDialog, whichItem, &itemType, &theItem, &itemBox);
        GetPenState(&savedPenState);
        PenNormal();
        PenSize(3, 3);
        FrameRoundRect(&itemBox, 16, 16);
        SetPenState(&savedPenState);
    }
        ...and when you want do use this userItem...
    
    #define MyDlgRSRC whatever	/*Number of the dialog resource*/
    #define MyDlgOutline whatever	/*The number of the userItem with the outline*/
    
        INTEGER itemType;	/*The type of the item in GetDItem*/
        Handle theItem;	/*The handle to the item*/
        Rect itemBox;	/*The box containing the item*/
    
        /*Get the dialog box*/
        theDialog = GetNewDialog(MyDlgRSRC, (DialogPtr) 0, (WindowPtr) -1);
    
        /*Add the OK button outline*/
        GetDItem(theDialog, MyDlgOutline, &itemType, &theItem, &itemBox);
        SetDItem(theDialog, MyDlgOutline, itemType, &DrawOKOutline, &itemBox);
     
        /*Everything is set; show the dialog*/
        ShowWindow(theDialog);
    
    If you want to get really fancy you can have the userItem procedure first look at the rectangle of the OK button, and then set its own rectangle accordingly.
    
    [For an automatic way of doing this look for the Default cdef article by Lloyd Lim]

    ModalDialog assumes the dialog is already visible and in the front.

    References: IM I-415

    A dialog has to be visible and above all other windows before you call ModalDialog. If you call ModalDialog without ensuring this, there will be big trouble.

    Use screenBits.bounds to center dialogs, alerts, etc. below the menu bar.

    References: IM I-163, IM I-527

    screenBits.bounds is the bounding rectangle of the screen that has the menu bar. This is the most convenient place to put dialogs and alerts. With some simple calculation, knowing screenBits . bounds and the width and height of your window, you can center it. You can find out the width and height of the standard file dialogs by examining their DLOG templates.

    If you save window locations in files, they may not be valid for all monitors.

    Refernces: IM V-208

    Saving the locations of windows between sessions is a nice thing for an application to do, but what if the user opens the document on a system with a very different screen setup? The window might not appear where it can be manipulated. So, if you do this, be sure to test to see if a window will appear in an accessible place on the screen, and if not, put it in a default location. You can do this by checking to see if a rectangle inset a few pixels within the title bar intersects the grayRgn.

    DragWindow expects startPt in boundsRect; if not it may not call SelectWindow.

    References: IM I-289

    If the startPt you pass to DragWindow is not within the boundsRect, a click in the title bar may not call SelectWindow and bring the window to the front. Dragging will still work, but clicking in place won't. The easiest fix is just to make sure boundsRect is big enough. You can call SelectWindow yourself, but this affects the feel of window dragging.

    SelectWindow does not automatically call SetPort. You must do that yourself.

    References: IM I-284

    Selection and drawing are independent of each other, although most of the time you want to do both. You can draw into windows that are not selected. QuickDraw does not set the port behind your back, which has the advantage of giving you flexibility. It has the disadvantage that you must remember to set the port yourself.

    DialogSelect responds to activate events but ignores suspend/resume events.

    References: IM I-417

    If you get suspend and resume events, make sure to activate or deactivate dialogs on the screen in response to them.

    Call PenNormal before calling DrawControls.

    References: IM I-322

    DrawControls assumes several things about the QuickDraw environment. Calling PenNormal is the best way to be sure that it does not mess up.

    The Control Manager only works when the origin of a window is at (0, 0)

    References: IM I-322

    If you use SetOrigin to set the origin of a window, you must call SetOrigin(0, 0) before doing anything that can affect or draw controls.

    To find the position of a window, use LocalToGlobal on the origin.

    References: IM I-165, IM I-193

    The safest way to find the position of a window is to do a SetPort to the window, get the origin of the window, and do a LocalToGlobal on that point.

    Always set the VisRgn and ClipRgn of offscreen ports.

    References: IM I-149

    The assertion in Inside Macintosh that the VisRgn has no effect on images that are not on the screen is wrong. To be safe, always set both regions of offscreen GrafPorts.

    Set the ClipRgn first when making a picture.

    References: TN 59

    The default ClipRgn for pictures is the largest possible region. If you create a picture using the default, as soon as you try to scale or move it using DrawPicture, the edges can overflow, causing all sorts of nasty things to happen. To avoid this, set the ClipRgn as the first thing when you create a picture.

    Don't make rowBytes in bitMaps greater than 8191.

    References: IM V-53

    The top three bits of rowBytes are used by the first release of Color QuickDraw to keep information about the bitmap. More recent versions of Color Quickdraw only use the top two bits, but do you want to gamble that everybody in the world has upgraded? I thought not.

    To dim text, draw a rectangle with penPat=gray and penMode=patBic over it.

    Dim text is another of those things which is strangely absent from QuickDraw. You can dim text or anything else by just painting over it with a gray penPat in patBic mode.

    To draw rotated text, draw to an offscreen bitmap, rotate it, and CopyBits it.

    Although there are ways to rotate text at any angle in PostScript, QuickDraw is only able to draw text in one orientation. The only way to get rotated onscreen text is to rotate the bitmap. There is no call to do this; you have to do it yourself.

    Don't use picSize to determine the size of a picture. Check the handle size.

    References: IM V-87

    The picSize field of a picture is only 16 bits wide. This is O.K. for most black-and-white pictures, but when Color QuickDraw was invented, it was discovered that this limitation was way too small. The actual length of a picture is now determined by the size of the handle. Although picSize is maintained for compatibility in version 1 pictures, don't count on it.

    Never draw outside a window except in XOR mode for temporary effects like drag.

    Drawing outside a window is very dangerous. For one thing, historically, most programs which have done this broke when extensions were made to QuickDraw. For another thing, in most cases, the user does not expect anything except the Finder to draw on the desktop. If you need to draw in a window the size of the screen, why not open a window the size of the screen and use that? In general, only DragGrayRgn and XOR lines for zooms should be done outside a window. These are temporary effects which always go away, so they are not so bad.

    To avoid animation flicker, synchronize drawing to the vertical retrace.

    References: IM II-350, IM V-567

    The video hardware is constantly sweeping through the screen memory displaying the bytes on the screen. If you try to access some memory during a QuickDraw operation that is currently being swept, there will be a conflict. Usually, QuickDraw is so fast that you will not notice it, but if you are doing animation using CopyBits, you might notice some flicker. You can try to avoid this by synchronizing the beginning of the CopyBits to the vertical retrace. The idea is always to have the drawing occur just ahead of the sweep so that no conflict occurs. A good way of finding out when the vertical retrace occurs is to install a VBL task on a small Macintosh, or use SlotVInstall on a Macintosh with slots. The routine will be called as a result of the vertical retrace, so it can signal another portion of the program to begin drawing. You will have to experiment with timing for your particular application.

    Lock handles to pictures before calling DrawPicture.

    References: IM I-190

    Rumor has it there is a bug in DrawPicture which involves following a pointer to the picture data rather than a handle and an offset. As DrawPicture can at times cause a memory compaction, this is unsafe. I find this bug hard to believe, as it would be a monstrous oversight, but better safe than sorry. Lock the picture.

    CopyBits on more than 3Kb data will work, but it might have to allocate memory.

    References: IM I-188

    On old systems, CopyBits had a problem with copying a picture bigger than about 3Kb. This was because CopyBits uses temporary memory from the stack. The bug required some programs to break down CopyBits calls into a number of thin wide strips, which was not very convenient. The bug has for a long time been fixed. What happens now is that CopyBits will first try to get enough memory from the stack and, if that is not enough, will allocate blocks using Memory Manager calls.

    The small Mac screen is 512 pixels wide by 342 pixels high including menu bar.

    Count 'em.

    Don't call any Memory Manager routines during an interrupt.
    Unlocked handles may not be valid during an interrupt.

    References: IM II-195

    An interrupt may interrupt anything, including a memory compaction. During a memory compaction, parts of the heap may be in an indeterminate state. Calling any Memory Manager routines could destroy the heap. Also, it is unsafe to look at unlocked handles. The memory in the handle may have been in the process of moving when the interrupt occurred.

    To synchronize to the vertical retrace on Macs with slots, use SlotVInstall.

    References: IM V-567

    Don't write in the application file. This will fail with read-only devices.

    References: TN-115,TN-116

    You never know where the file containing your application resides. It could be on a CD-ROM, or it could be on a shared volume. Applications that write to themselves, for example to set user preferences, will fail under these circumstances. Besides, there is a better way to save preferences, described in another one-liner.

    Use PBGetVInfo to convert a VRefNum to a volume name.

    References: IM IV-129

    Delete uses the Poor Man's Search Path, so don't delete blindly.

    References: IM IV-113, IM IV-147

    As the high level FSDelete is an old MFS call, it will use the Poor Man's Search Path. This means that, if it does not find a file of the right name in the current directory to delete, it will try to delete such a file in the System Folder. This can be disastrous. Before you call Delete, make absolutely sure that it will delete the correct file by checking to see if such a file exists where you expect it.

    File Manager routines with dirID=0 use the Poor Man's Search Path.

    Actually, every File Manager routine with dirID=0 or no dirID specified uses the Poor Man's Search Path. Most of the time this just makes things more convenient, as it allows the System File to be searched by default after the current directory. Beware of what is happening, though.

    Truncate and reallocate files before overwriting to reduce fragmentation.

    References: IM IV-111, IM IV-112

    As you overwrite a file again and again with sometimes less and sometimes more data, the file can become fragmented. Lots of fragmented files will worsen the performance of the entire file system. To help avoid this fragmentation, every time you need to overwrite a file from the beginning to the end, first truncate it to zero bytes using the SetEOF function and then allocate the number of bytes you expect to write. The FileManager will try to allocate a nice contiguous chunk of blocks.

    Check/change the creator and type of Save As... files before overwriting.

    References: IM IV-113

    Just checking for the existence of a file with the same name is not enough. You must make sure that the file type and creator are correct as well. Otherwise, the file will not appear correct to the Finder, and you may not be able to read it in the future. There are a variety of strategies to do this. Some programs simply don't let you Save As over a file of a different type or creator. Some quietly change the type and creator. Some put up an alert. You decide.

    If you rewrite files by deleting and creating, copy all Finder information.

    References: IM IV-113

    Deleting and recreating is not the best way to rewrite a file, but if you must do it, be sure to copy all the Finder information. This means the entire contents of the FInfo record. One more piece of information that needs to be copied is the GetInfo comment. I don't think there is a standard way of doing this, which is one of the reasons deleting and recreating is a bad thing to do.

    Directory ID's are longs, not shorts. Shorts work ALMOST all the time.

    References: IM IV-92

    If a file version number appears in a file manager call, always set it to 0.

    References: IM IV-90

    The version number of a file was an old mechanism to distinguish between two files with the same name, for example to use one as a backup for the other. It was a good idea, but it was never completely implemented. The problem is that different bits of software do different things with the version number. The Finder ignores the version number, but the standard file routines only show files with version 0. This is the most common cause of bugs involving a file that you can see in the Finder but not within an application. To prevent this from happening, if a version number appears in a call, always explicitly set it to 0 before you make that call.

    To convert a pathRefNum to a name or file number, use PBGetFCBInfo.

    References: IM IV-179

    Prevent the creation of files with names that begin with a period.

    References: IM II-175, IM II-245

    The File Manager interprets any file name beginning with a period as the name of a device driver. Unfortunately, it is also possible to create real files with names that begin with a period, causing no end of confusion. To protect your users from this, add a check to your Save As routines to reject file names that begin with a period.

    Write/update the Finder info before writing data or resource forks.

    References: IM IV-113

    Lock handles before passing their dereferenced pointers to any routine.

    References: IM XREF

    It is well known that some Toolbox routines are known to change memory. The Inside Macintosh X-Ref has a list of such routines, which it claims is complete. Hah! The list is really growing all the time, and you cannot count on any routine's being safe any more. Even if you could, you run the risk that your language accesses that routine through glue which can be loaded the first time you call the routine, thus changing memory. The best way to be safe from this is to lock all handles before dereferencing their pointers and passing them to any routine, however benign the routine may seem.

    Lock handles before setting referenced data to expressions containing functions

    This is a nasty one. Let us say you have a C statement like (*aHandle)[5] = foo(3); in other words, aHandle is a handle to an array, and you want to set element number 5 of that array to the return value of foo. This is unsafe! The reason is that C will calculate the pointer to the fifth element of aHandle before it calls foo. If foo compacts memory, this pointer will no longer be valid. This problem is not limited to C; it can occur with any language that allows similar constructs. Don't count on a certain order of evaluation. Lock the handle, or use an intermediate variable.

    Put an odd long at location zero on a 68000 to help find NIL handle references.

    Whenever you use a NIL handle or pointer, it will look at whatever is stored in location 0. The long word at location 0 could conceivably point anywhere, causing all sorts of indirect problems to result from nil handle references. If you suspect your application has NIL handle references, you can track them down by using a Macintosh that uses the old 68000 processor. If you put an odd value at location 0, whenever your application tries to use a NIL handle, you will get an exception immediately, and you will break into the debugger.

    Call MoveHHi before locking a handle to avoid memory fragmentation.

    References: IM II-44

    If you lock a handle, it may be in the middle of the heap. If you lock several handles, they may fragment memory and affect subsequent memory allocations. You can avoid this by first calling MoveHHi on the handle before locking it. MoveHHi will take some time, but it will try to move the handle as high in memory as possible before you lock it.

    Use HGetState/HLock/HSetState to lock a handle then put it back as it was.

    References: IM IV-79

    Most of the time, the best way of using handles is always to keep them unlocked and only lock them when it is absolutely necessary for a short time. However, sometimes you have a handle that you don't know is locked and you need to lock it and then set it back to the previous state. To do this, use HGetState to get the state of the handle before locking it. Then, instead of using HUnlock, use HSetState to restore the handle to its previous state.

    Always use unsigned characters within text and Pascal-format strings.

    The first character of a Pascal format string is its length in bytes, from 0 to 255. This length only makes sense if you are using unsigned characters. If you are using signed characters, such as the default C char, numbers above 127 will be interpreted as negative numbers. This is very dangerous, especially when you use packages such as TextEdit, which take a length. Most languages will quietly promote a signed character to a signed short or long and will happily pass this value to TextEdit, which will interpret it as a VERY LARGE length, whereupon TextEdit will crash and burn. To avoid this, always use unsigned characters.

    Save application preferences in a folder named Preferences in the System Folder

    As it is a bad idea to keep application preferences in the file itself, where do you keep them? After a discussion of this matter on Usenet, the consensus seemed to be that there should be a folder named Preferences in the System Folder and each application should have file with an application-specific name in that folder. To make it as general as possible, the algorithm should work like this: First look in the current directory for the preferences file. If it is found, use that. The Poor Man's Search Path will ensure that any file in the System Folder will be found as well. If no file is found, look for a Preferences folder in the System Folder and look for the file there. If none is found, search all folders in the System Folder for the file. (This allows the user to rename the Preferences folder.) If it is not found, create a Preferences folder and save a file with the default preferences in that folder.

    Use SysEnvirons to find the System (Blessed) Folder.

    References: IM V-5

    Use GetAppParms to get the name of the application.

    References: IM II-58

    The high bit of SysParam . volClik enables the alarm clock.

    References: IM II-370

    Check the application name at $910 before exiting with ES within MacsBug.

    Hitting the programmer's interrupt switch and doing an ES within MacsBug is a good way to stop a runaway application. Unfortunately, with MultiFinder, you never know just what is running when you hit that switch. To find out, look at location $910 to see the name of the current application. If it is not the one you want to stop, enter G and try again.

    To exit to shell in the mini-debugger, enter SM 0 A9 F4 and then G 0.

    The mini-debugger has very few commands. One of the most useful functions, exit to shell, is not provided. There are a lot of ways of doing an exit to shell, but most of them involve remembering magic numbers of addresses for different systems. The way presented here should work on any machine. A9F4 is the code for the ExitToShell trap. SM 0 A9 F4 puts that instruction in the RAM at location 0. G 0 jumps to that instruction and executes it.

    In Pascal, don't nest procedures to be passed by procedure pointer.

    In Pascal, "with theHandle^^" is unsafe if memory compaction can occur.

    Because a with statement dereferences a handle at the beginning of the block of code it controls, if the code causes a memory compaction, this handle may no longer be valid. Lock the handle first.

    Avoid writing tail patches for traps.

    References: TN 212

    Some Apple patches to traps check the stack to see who called them and have some special cases. I know that this is not very sociable of them, but the upshot is that your application will get blamed if you use a tail patch and this causes one of Apple's bug fixes to fail.

    There is no official way to tell if MultiFinder is running or not.

    I wish there were a way to tell this, because it affects the behavior of Launch, but there isn't. There have been a variety of unofficial ways of telling, but most of them have failed as the system was changed. The only absolutely safe way to tell seems to be to assume that you are not running under MultiFinder until you receive a MultiFinder event.