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

BitMap Rotation in C (and support routines)

by Juri Munkki

I'm including two small Think C programs along with sources, project files and resources.

I tried to make it as easy as possible to include these programs with the usenet programmer's guide. The resource files are extremely simple. You only need one window resource and one picture resource. The windows should have id 1000 and they should be visible, but all the other parameters can be whatever you want. The PICTs should have id 1000 and can be just about anything.

The other program documents the internal region data format by providing the functionality of the BitmapToRegion call. This may actually be useful to those programmers who wish to have their programs running on machines without 32 bit QD, although I recommend using Apple's licenseable code instead of mine. In addition, understanding the region data format will allow programmers a better understanding of quickdraw algorithms.

The other program performs a function that is not available from Apple or from anywhere else that I know of. So far programs have had to have their own bitmap rotation routines. A 90 degree rotation routine was already included with the guide, but this routine allows free rotation of any bitmap. Current performance is limited by the drawing speed. To optimize this routine, draw into an offscreen bitmap using your own commands. If there are enough requests for this, I could write something to do it more efficiently. Using PaintRect or MoveTo/LineTo is definitely not the way to do it.

I hope you find these interesting.

[Juri's code lined up beutifully until it was imported into Word. ]

{------------------- Bit map Rotation -----------------------------------}

/*
>>   BitRot.c   Bitmap Rotation Algorithm Tester.
>>            Copyright (c)1990, Juri Munkki
>>            Permission to use is granted for noncommercial applications.
>>
>>   This program rotates a bitmap to any angle. With modifications, it
>>   can be used to scale as well as rotate.
>>
>>   The idea is to perform the inverse tranformation to the destination
>>   bitmap. The trick is to avoid multiplication in the main loop. This
>>   program also optimizes so that it doesn't copy any extra pixels.
>>
>>   The routine does a simplified flood fill to copy every pixel in the
>>   destination from the source. There are no guarantees that every pixel
>>   from the source is used, but every pixel in the destination is checked.
>>
>>   The program works it's way from near the center of the rotated rectangle.
>>   To illustrate:
>>          .           .           .           .
>>         / \         / \         /_\         /_\
>>        /   \       /   \       /___\       /___\
>>       /  .  \     /_____\     /_____\     /_____\
>>       \     /     \     /     \     /     \_____/
>>        \   /       \   /       \   /       \___/
>>         \ /         \ /         \ /         \_/   
>>          '           '           '           '
>>
>>   The fill operation starts from the center of the rectangle and works it's
>>   way up and down. While on the way, the point may also move left or right
>>   depending on a displacement value that is calculated beforehand. The fill
>>   always ends at topmost and bottommost corners. To Try out how the fill works,
>>   use a relatively dark picture and/or add some delays in the plotting subroutine.
>>
>>   To compile:
>>      long is 32 bits
>>      fixed point numbers are 16+16 bit.
>>      integers are 16 bits.
>>      Needs a 'WIND' id 1000 window template resource.
>>      Needs a 'PICT' id 1000 as the picture to rotate.
>>
>>   Limitations: For extremely large bitmaps, there might be a problem with fixed
>>   point resolution. To improve resolution, use larger fixed point numbers or
>>   extended precision floating point.
*/

#define   PIC_ID   1000         /* Picture resource ID 1000 is used as the demo picture  */

/*   Prototypes:
*/
void   PictBit(BitMap *,int);    /*  Creates a bitmap and draws the picture into
it.         */
int      origpixel(long,long);   /*  Almost same as GetPixel, but optimized for our
purposes   */

typedef   struct        /*   A Handy structure that keeps track of coordinates      */
{                       /*   in the source and destination rectangles.            */
   int      x,y;        /*   Location on destination bitmap in integral coordinates   */
   long   xo,yo;        /*   Location on source bitmap in fractional coordinates      */
}   place;

long   maxx,maxy;       /*   Size of source bitmap as a fractional (16:16) number.   */

WindowPtr   mywind;     /*   Any port in a storm for drawing the rotated bitmap.      */
BitMap      mybits;     /*   Bitmap to hold the source bits.                     */
long      sinr,cosr;    /*   Sin and Cosine values.       65536==1.0               */

/*
>>   ScanLeftRight tries to go as far left in the destination as the source
>>   rectangle permits. While doing this, it copies the pixels from the source
>>   to the destination. The same thing is done from the center to the right.
*/
void   ScanLeftRight(start)
place   *start;
{
   place   left,right;
   
   left=right=*start;               /*   Starting point.         */
   if(origpixel(left.xo,left.yo))   /*   Copy starting point.   */
   {   PlotDot(left.x,left.y);
   }

   /*   Check source rectangle boundaries.                  */
   while(left.xo>=0 && left.yo>=0 && left.xo<maxx && left.yo<maxy)
   {   if(origpixel(left.xo,left.yo))
      {   PlotDot(left.x,left.y);   /*   Copy pixel.            */
      }
      left.x--;                 /*   Move left.            */
      left.xo-=cosr;            /*   Move within source      */
      left.yo-=sinr;
   }

   right.x++;                  /*   Move right.            */
   right.xo+=cosr;
   right.yo+=sinr;

   /*   Check source rectangle boundaries.                  */
   while(right.xo>=0 && right.yo>=0 && right.xo<maxx && right.yo<maxy)
   {   if(origpixel(right.xo,right.yo))
      {   PlotDot(right.x,right.y);
      }
      right.x++;               /*   Move right            */
      right.xo+=cosr;
      right.yo+=sinr;
   }
}
void   main()
{
   long      theangle;             /*   Angle of rotation.               */
   place      goup,godown;         /*   Coordinates.                     */
   int         disp,edgex;         /*   Displacement (see below).        */
   Point      mouse,oldmouse;      /*   mouse locations for test.        */
   
   InitGraf(&thePort);      InitCursor();
   InitFonts();         InitWindows();
   InitMenus();         TEInit();
   InitDialogs(0L);                /*   Start up managers.              */
   mywind=GetNewWindow(1000,0,-1); /*   Open up a window.               */
   SetPort(mywind);                /*   Draw in this new window.        */
   
   PictBit(&mybits,PIC_ID);      /*   Read the picture into a bitmap.         */
   SetupGetPixel();              /*   Prepare for fast read of bitmap.      */

   maxx=((long)mybits.bounds.right)<<16;   /*   Source boundaries are changed   */
   maxy=((long)mybits.bounds.bottom)<<16;   /*   into fixed point numbers      */
   
   while(!Button())            /*   Quit when button is down.            */
   {   GetMouse(&mouse);         /*   Find out mouse location.            */
      if(mouse.h!=oldmouse.h)      /*   Has horizontal position changed?      */
      {   oldmouse.h=mouse.h;
         theangle=mouse.h*1024L;   /*   Rotation angle from horizontal value.   */
      
         EraseRect(&mywind->portRect);   /*   Erase window contents.         */
         
         sinr=FracSin(-theangle)>>14;   /*   Sin for inverse rotation.      */
         cosr=FracCos(-theangle)>>14;   /*   Cosine for inverse rotation.   */
      
         goup.x=mywind->portRect.right/2; /*   Destination center x coordinate.*/
         goup.y=mywind->portRect.bottom/2;   /*   --   ''   --      y coordinate.*/
         goup.xo=(long)mybits.bounds.right<<15;   /*   Center of source bitmap.*/
         goup.yo=(long)mybits.bounds.bottom<<15;   /*   Center of source bitmap.*/
         
         godown=goup;            /*   copy center to "godown".         */
         
         /*   Adjust starting position according to rectangle size and angle.
         **   Basically we transform one corner of the rectangle to find out
         **   where the fill should end.
         */
         if(cosr*sinr>0) disp=(-mybits.bounds.right*cosr + mybits.bounds.bottom*sinr)>>17;
         else  disp=(-mybits.bounds.right * cosr - mybits.bounds.bottom * sinr) >> 17;   

         if(sinr>0)      disp=-disp;
   
         ScanLeftRight(&goup);   /*   Copy first line to destination.         */
         
         edgex= (disp>0) ? disp : -disp;   /*   edgex=ABS(disp)               */

         /*   Go up until source rectangle bound is crossed.               */
         do
         {   while(goup.xo>=0 && goup.yo>=0 && goup.xo<maxx && goup.yo<maxy)
            {   ScanLeftRight(&goup);
            
               goup.y--;      /*   Go up.                           */
               goup.xo+=sinr;   /*   Move in source bitmap coordinates.      */
               goup.yo-=cosr;
            }
            
            if(disp>0)         /*   Stay inside bounds as long as possible.   */
            {   goup.x++;      /*   This is done by adjusting the location   */
               goup.xo+=cosr;   /*   of the fill.                     */
               goup.yo+=sinr;
            }
            else
            {   goup.x--;
               goup.xo-=cosr;
               goup.yo-=sinr;
            }
         }   while(edgex-- > 0);   /*   Adjust only as long as it is useful.   */
         
         edgex= (disp>0) ? disp : -disp;   /*   edgex=ABS(disp)               */
         
         godown.y++;            /*   Go down.                        */
         godown.xo-=sinr;
         godown.yo+=cosr;
      
         /*   Go down until source rectanlge bound is crossed.            */
         do
         {   while(godown.xo>=0 && godown.yo>=0 && godown.xo<maxx && godown.yo<maxy)
            {   ScanLeftRight(&godown);
      
               godown.y++;         
               godown.xo-=sinr;
               godown.yo+=cosr;   
            }
            
            if(disp<0)
            {   godown.x++;
               godown.xo+=cosr;
               godown.yo+=sinr;
            }
            else
            {   godown.x--;
               godown.xo-=cosr;
               godown.yo-=sinr;
            }
         }   while(edgex-- > 0);
      }
   }
}

{------------------- Bit Support for rotation -----------------------------------}

/*
>>  BitSupport.c  Bitmap Rotation Algorithm Tester.
>>          Support routines for bitmap rotation
>>          Copyright (c)1990, Juri Munkki
>>          Permission to use is granted for noncommercial applications.
>>
>>  Ugly routines to test and set pixel values. You should start by optimizing
>>  these routines, if you wish to increase the speed of this program. The
>>  PlotDot routine is the real bottleneck of this program. Origpixel is quite
>>  fast, since it doesn't use toolbox routines.
*/

extern  BitMap    mybits;
    Ptr      *index;
    
void  PlotDot(x,y)
int    x,y;
{
  Rect  foo;
  
  foo.left=x;
  foo.right=x+1;
  foo.top=y;
  foo.bottom=y+1;
  PaintRect(&foo);
}

int    origpixel(x,y)
long  x,y;
{
asm  {  move.w  y,D0
    asl.w  #2,D0
    move.l  index,A0
    move.l  0(A0,D0),A0
    move.w  x,D0
    move.w  D0,D1
    lsr.w  #3,D0
    add.w  D0,A0
    moveq.l  #7,D0
    and.w  D0,D1
    sub.w  D1,D0
    btst  D0,(A0)
    beq    @nothing
    moveq.l  #-1,D0
    return
@nothing
    clr.w  D0
    return
  }
/*  return BitTst(index[y>>16],x>>16);*/
}

void  SetupGetPixel()
{
  int    i;
  Ptr    base;

  index=(Ptr *)NewPtr(mybits.bounds.bottom*sizeof(long));
  base=mybits.baseAddr;
  for(i=0;i<mybits.bounds.bottom;i++)
  {  index[i]=base;
    base+=mybits.rowBytes;
  }
}

/*  PictBit reads a picture resource, creates
>>  a large enough bitmap and draws the picture
>>  into it. The Bits bitmap is supplied to the
>>  routine. Space for the actual bits is reserved
>>  with NewPtr. Be sure to deallocate it once
>>  it is no longer needed!
>>
>>  No error checking is made.
*/
void  PictBit(Bits,PictId)
BitMap  *Bits;
int    PictId;
{
  GrafPort  AnyPort;
  GrafPtr    SavedPort;
  PicHandle  ThePic;
  Rect    TempRect;
  long    RAMNeeded;
  
  GetPort(&SavedPort);
  OpenPort(&AnyPort);
  
  ThePic=(PicHandle)GetResource('PICT',PictId);
  TempRect=(*ThePic)->picFrame;

  OffsetRect(&TempRect,-TempRect.left,-TempRect.top);
  Bits->bounds=TempRect;

  Bits->rowBytes=((TempRect.right + 15) >> 4) << 1;  /*  Round to word boundary  */
  RAMNeeded=Bits->rowBytes*TempRect.bottom;      /*  Calculate RAM for bits  */
  Bits->baseAddr=NewPtr(RAMNeeded);
  
  SetPortBits(Bits);
  AnyPort.portRect=TempRect;
  RectRgn(AnyPort.visRgn,&TempRect);
  RectRgn(AnyPort.clipRgn,&TempRect);

  EraseRect(&TempRect);
  DrawPicture(ThePic,&TempRect);

  ReleaseResource(ThePic);
  ClosePort(&AnyPort);
  SetPort(SavedPort);
}

{------------------- Bit map to region -----------------------------------}

/*
>>  BitRegion.c, 04/23/89                        <<
>>  My routine for converting a bitmap into a region.          <<
>>                                    <<
>>  Juri Munkki, jmunkki@kampi.hut.fi                  <<
>>  Senior Systems Analyst                        <<
>>  Helsinki University of Technology Computing Centre          <<
>>  Otakaari 1 U044A, SF02150 Espoo, Finland              <<
>>                                    <<
>>  This program is in the PUBLIC DOMAIN, but:              <<
>>    I would really like to join the NeXT registered developer    <<
>>    program. I returned the forms, but I haven't heard anything    <<
>>    from NeXT. Please help me, if you can affect their decision.  <<
>>                                    <<
>>  Known bug:  This program knows how to create regions larger than  <<
>>        32 KB. QD doesn't support anything larger than 32KB.  <<
*/

#define  PIC_ID  1000      /*  Resource ID of test picture    */
RgnHandle  BitRgn(BitMap *);  /*  Function prototype for BitRgn  */

/*  PictBit reads a picture resource, creates
>>  a large enough bitmap and draws the picture
>>  into it. The Bits bitmap is supplied to the
>>  routine. Space for the actual bits is reserved
>>  with NewPtr. Be sure to deallocate it once
>>  it is no longer needed!
>>
>>  No error checking is made.
*/
void  PictBit(Bits,PictId)
BitMap  *Bits;
int    PictId;
{
  GrafPort  AnyPort;
  GrafPtr    SavedPort;
  PicHandle  ThePic;
  Rect    TempRect;
  long    RAMNeeded;
  
  GetPort(&SavedPort);
  OpenPort(&AnyPort);
  
  ThePic=(PicHandle)GetResource('PICT',PictId);
  TempRect=(*ThePic)->picFrame;

  OffsetRect(&TempRect,-TempRect.left,-TempRect.top);
  Bits->bounds=TempRect;

  Bits->rowBytes=((TempRect.right + 15) >> 4) << 1;  /*  Round to word boundary  */
  RAMNeeded=Bits->rowBytes*TempRect.bottom;      /*  Calculate RAM for bits  */
  Bits->baseAddr=NewPtr(RAMNeeded);
  
  SetPortBits(Bits);
  AnyPort.portRect=TempRect;
  RectRgn(AnyPort.visRgn,&TempRect);
  RectRgn(AnyPort.clipRgn,&TempRect);

  EraseRect(&TempRect);
  DrawPicture(ThePic,&TempRect);

  ReleaseResource(ThePic);
  ClosePort(&AnyPort);
  SetPort(SavedPort);
}

/*
>>  Convert a bitmap into a region.
>>  The bitmap origin should be at the top left corner and it
>>  shouldn't be wider than 8192 pixels. You might want to add
>>  some error checks for weird or illegal bitmaps.
*/
RgnHandle  BitRgn(Bits)
BitMap    *Bits;
{
  register Handle  Target;        /*  This is where we write the region  */
  register short  *TargetP;      /*  Pointer to region data array    */
  register long  MaxTarget;      /*  Memory management stuff        */
  register long  CurTarget;      /*  Index into the region data array  */
  long      RowStart;      /*  Index of first x value on row    */
  long      TargetSize,RgnSize;  /*  Size in data words & bytes      */
  Rect      TempRect,RgnBounds;  /*  Temporary & region bounds rects    */
  BitMap      RowBitMap;      /*  Working bitmap with one row in it  */
  char      RowBitData[1024];  /*  Buffer for pixels above (8192 pix)  */
  int        i;          /*  Row counter in a "for" loop      */
  register int  x;          /*  Column counter in a "for" loop    */
  register int  pixelstatus;    /*  Flag is false if last pixel is white*/
  
  TargetSize=4096;          /*  Initial guess for final region size  */
  Target=NewHandle(TargetSize);    /*  Allocate initial data buffer    */
  if(Target==0)  return 0;      /*  Did we run out of RAM? 0=failure.  */
  TargetP=(short *)(*Target + 10);  /*  TargetP points to region data    */
  HLock(Target);            /*  We just derefenced target. Lock it.  */
  MaxTarget=(TargetSize-20)/2;    /*  A safe maximum value for our index  */
  CurTarget=0;            /*  Start with target index 0 (no data)  */
  
  /*  Set region bounds to nothing:                    */
  SetRect(&RgnBounds,32767,32767,-32767,-32767);
  
  /*  Set up a bitmap with a single line:                  */
  TempRect=Bits->bounds;      /*  Set up left & right bounds      */
  TempRect.top=0;          /*  Single row bitmap with top=0    */
  TempRect.bottom=1;        /*  Single row bitmap with bottom=1    */
  RowBitMap.bounds=TempRect;
  RowBitMap.baseAddr=RowBitData;
  RowBitMap.rowBytes=((TempRect.right + 15) >> 4) << 1;
  
  /*  Start out with the first line of the source bitmap          */
  CopyBits(Bits,&RowBitMap,&TempRect,&RowBitMap.bounds,srcCopy,0);
  
  for(i=Bits->bounds.bottom;i>=0;i--)      /*  For every line and more      */
  {  TargetP[CurTarget++]=TempRect.top;    /*  Row data starts with Y value  */
    RowStart=CurTarget;            /*  X values on row start here    */
    pixelstatus=0;              /*  Pixels outside bitmap are white  */
    for(x=Bits->bounds.left;x<Bits->bounds.right;x++)
    {  if((BitTst(RowBitData,x)!=0) != pixelstatus)  /*  Test for a change  */
      {  pixelstatus= !pixelstatus;          /*  Color changed    */
        TargetP[CurTarget++]=x;            /*  Record x coordinate  */
        if(CurTarget>=MaxTarget)          /*  Is the buffer full?  */
        {  TargetSize+=2048;            /*  Enlarge the buffer    */
          HUnlock(Target);            /*  Unlock to change size  */
          SetHandleSize(Target,TargetSize);    /*  Change the size      */
          if(MemErr)                /*  No success?        */
          {  DisposHandle(Target);        /*  Dispose of what we have  */
            return 0;              /*  return failure.      */
          }
          TargetP=(short *)(*Target + 10);    /*  Dereference handle    */
          HLock(Target);              /*  Lock it again      */
          MaxTarget=(TargetSize-20)/2;      /*  New maximum index    */
        }
      }
    }
    if(pixelstatus)    TargetP[CurTarget++]=x;  /*  Last pixel was black, record edge  */
    if(RowStart==CurTarget)            /*  Row was empty (no changes)      */
      CurTarget--;              /*  Remove Y value from data      */
    else
    {  /*  Check for new region bounds:                        */
      if(TargetP[RowStart]   <RgnBounds.left)    RgnBounds.left=TargetP[RowStart];
      if(TargetP[CurTarget-1]>RgnBounds.right)  RgnBounds.right=TargetP[CurTarget-1];
      
      RgnBounds.bottom=TempRect.top;

      TargetP[CurTarget++]=32767;        /*  Write an "end of line" flag      */
    }

    /*  Copy current line into the single line bitmap:              */
    if(i>0) CopyBits(Bits,&RowBitMap,&TempRect,&RowBitMap.bounds,srcCopy,0);

    TempRect.top++; TempRect.bottom++;      /*  Move one line down      */
    
    /*  If we are still inside the bitmap, XOR this line with the previous line:*/
    if(i>1)  CopyBits(Bits,&RowBitMap,&TempRect,&RowBitMap.bounds,srcXor,0);
  }

  RgnBounds.top=TargetP[0];  /*  Top boundary is first recorded Y coordinate    */

  /*  If the region is empty, set the bounds rect to an empty rectangle:      */
  if(RgnBounds.right<=RgnBounds.left || RgnBounds.bottom<=RgnBounds.top)
    SetRect(&RgnBounds,0,0,0,0);

  TargetP[CurTarget++]=32767;  /*  Write an "end of region" flag          */
  HUnlock(Target);      /*  Unlock our target region.            */
  RgnSize=CurTarget*2+10;    /*  Calculate region size.              */
  if(RgnSize<=28)  RgnSize=10;  /*  Rectangular or empty region is only a Rect    */

  (*(RgnHandle)Target)->rgnBBox=RgnBounds;/*  Store region bounds rectangle    */
  (*(RgnHandle)Target)->rgnSize=RgnSize;  /*  Store region size (low 16 bits)    */
  SetHandleSize(Target,RgnSize);      /*  Resize region to optimally small  */
  return (RgnHandle)Target;        /*  Return resulting region handle    */
}

/*  This is just a short test program "main":
*/
void  main()
{
  WindowPtr  TestWindow;    /*  Simple window used for testing  */
  RgnHandle  TheRegion;    /*  Region handle for test region  */
  BitMap    TheBits;    /*  Bitmap for testing        */
  EventRecord  MyEvent;

  /*  "Magic Incantations" (Copyfight Apple Computer, Inc.)  */
  InitGraf(&thePort);    InitCursor();  InitFonts();      InitWindows();
  InitMenus();      TEInit();    InitDialogs(0L);    InitCursor();

  TestWindow=GetNewWindow(1000,0,-1);
  SetPort(TestWindow);
  
  PictBit(&TheBits,PIC_ID);  /*  Read the picture into a bitmap      */
  TheRegion=BitRgn(&TheBits);  /*  Convert the bitmap into a region    */
  if(TheRegion)        /*  If we get a region, let's play with it  */
  {  InvertRgn(TheRegion);  /*  Display region              */
    FlushEvents(everyEvent,0);
    while(GetNextEvent(mDownMask,&MyEvent)==0);

    GlobalToLocal(&MyEvent.where);
    InvertRgn(TheRegion);  /*  Hide region, then drag it around.    */    
    DragGrayRgn(TheRegion,MyEvent.where,
          &TestWindow->portRect,
          &TestWindow->portRect,
          noConstraint,0L);
  }
}


INIT Skeleton Code

by Jon Wätte

SetWindow INIT, which lets you place windows anywhere on the screen when an application calls ShowWIndow on it. (Just like TWM under X)

The INIT consists of a loader (that should be compiled as an INIT resource) and two patches (of which the loader will choose to install one depending on color QD availability)

The patches should be compiled as "tpat" code resources, and the b/w version should have resource id 128, the color version id 129.

Stuff the three resources (INIT, and 2 tpat's) into a file of type INIT, and drop it into your system folder. Reboot and enjoy !

(Actually, this INIT should check for the option key being down before wanting to place a window, that would make it much more useful)

It is tested on a SE/30 with 24bit color and 32bit QD, and on a plain 1meg SE, and both works fine (The SE hasn't Color QD, and thus uses the b/w version)

Happy hacking,

Jon Wätte, Stockholm, Sweden, h+@nada.kth.se

/*

	SetWindow.c

	This INIT loads the tpat resource ID 128 for B/W and tpat 129 for color
	systems and patches the ShowWindow trap with that resource.

	Copyright 1990 Jon Wätte. Permission granted to use and distribute if
	you don't charge anything for it. If you do, a quarter of your gross
	sales is mine.
*/


int
strcmp(char * s1, char * s2) /* Since we don't want to link with the ANSI
							library for just one function, we do it
							ourselves. Note, this verision returns 0 on
							MISmatch ! */
{
	while(*s1 == *s2 && *s2) {
		s1++; s2++;
	}
	if(*s1 == *s2) return 1;
	return 0;
}


main()
{
	char * moof;
	long oldaddr;
	Handle foom;
	int num = 128;
	SysEnvRec theWorld;
	
	SysEnvirons(2, &theWorld); /* Check what we have here */
	if(theWorld.hasColorQD) num++; /* If we have color QD, use the CQD version
										which has another number, and supports
										multiple screens */
	foom = GetResource('tpat', num);

	if(foom == 0) { /* Maybe we built the resources with the wrong number ? */
		SysBeep(30); /* Beep to show we didn't load */
	} else {
		HUnlock(foom); /* May be marked as "locked" */
		DetachResource(foom); /* We don't want a resource hanging around in the
								system heap in that way... */
		MoveHHi(foom); /* Get the code as much out of the way as possible */
		HLock(foom); /* Lock it down firmly, so it won't move... */

		for(moof = *foom; !strcmp(moof, "Moof!"); moof++); /* Check for the
								availability of our "signature" */

		* (long *) moof = NGetTrapAddress(0x115, ToolTrap); /* Save the address
								to jump to in place of the signature */
		NSetTrapAddress(StripAddress(*foom), 0x115, ToolTrap); /* Set the new
								trap address to our routine - note, since the
								machine possibly might be SwapMMU'ed, we do a
								StrpAddress - it can't hurt anyway */
	} /* That's it ! Not so hard at all. */
}

{---------------- Set window tpat --------------------------------}

/*
	SetWindow tpat

	This trap patch will make ShowWindow act like X-windows,
	so you may place a new window wherever you like.

	This INIT does lots of stupid things, like pokes in lo-mem globals
	not very well-documented, and draws in the WMgrPort.

	Copyright 1990 Jon Wätte - distribution and usage allowed if you
	don't charge for it. If you do, quarter of your gross sales is mine.

	I'm reachable as Internet: h+@nada.kth.se USEnet: mcsun!sunic!draken!h+

*/

/*
	Things on the to-do list: Maybe turn on/off various features with a
	control panel cdev ? Maybe the mouse should move back again after
	positioning the window ? Maybe we shouldn't beep at dumb applications ?
	Maybe we should show Modal windows without positioning them ?
*/

/* lo-mem globals that are documented, somewhere... */
extern  Point   MTemp           :   0x828;
extern  Point   RawMouse        :   0x82c;
extern  int     CrsrNewCouple   :   0x8ce;

main(WindowPtr w) /* This is the patch. The declaration should look the same
					as if you were writing the actual routine. Note, that for
					routines taking more than one argument, pascal declaration
					is needed. */
{
	char blackPat[8];

	int ofx = w->portRect.right - w->portRect.left,
		ofy = w->portRect.bottom - w->portRect.top; /* Calculate the dimensions
														of the window */
	int x;

	asm {
			bra		@done
	moof:	dc		'Mo', 'of', '!\000' /* This is used for communication
											with the loader, to see where to
											jump next */
	done:	nop
	}

/* Do the stuff here ! */

	for(x=0; x < 8; x++) blackPat[x] = 0x55 << (x & 1); /* Set up a grey
															pattern */
	if(!(((WindowPeek) w)->visible)) { /* Only if you show a hidden window */

		Point p; /* We have to save away various data to get the thing to
					work right, and reset the WMgrPort in its state. Otherwise,
					the various managers would get VERY confured */
		GrafPtr oldPort;
		GrafPtr myPort;
		PenState pnState;
		RgnHandle clipRgn = NewRgn();

		GetPort(&oldPort); /* Whatever port was used - note, usually apps
								do a SetPort after a ShowWindow, but it never
								hurts to be nice */
		GetWMgrPort(&myPort); /* This is the port we're gonna draw in */
		SetPort(myPort);

		GetClip(clipRgn); /* We have to change the clip region so we're sure
							that drawing actually takes place */
		ClipRect(&(myPort->portRect)); /* No way of knowing more than one
							monitor without Color QuickDraw */

		p = * (Point *) &(w->portBits.bounds); /* Where to move the mouse */
		p.h = -p.h; /* The offsets are negative in bounds ... */
		p.v = -p.v;
	    RawMouse = p; /* Hit the mouse lo-mem globals (danger !!!) */
    	MTemp = p;
    	CrsrNewCouple = 0xffff;

		do {
			long l;
			Delay(2, &l); /* Give the mouse a chance to catch up to the place
							where the window's default position is */
		} while(Button()); /* See to it the button's up before we go
								further */

		GetPenState(&pnState);
		HideCursor(); /* We don't want the cursor obscured */
		ShowPen();
		PenSize(2, 2);
		PenMode(patXor); /* Drawing in the WMgrPort requires undo-able
							ilnes only obtainable by xoring */
		PenPat(blackPat); /* it's really a grey pattern... */

		while(!Button()) {
			Rect r;
			long l;

			GetMouse((Point *) &r);
			r.bottom = r.top + ofy;
			r.right = r.left + ofx;
			FrameRect(&r); /* Show the outline */
			Delay(2, &l); /* For a short while */
			FrameRect(&r); /* Restore the screen */
		}; /* Until the user clicks */

		{
			GetMouse(&p); /* Where did the click go down ? */
			LocalToGlobal(&p);
			MoveWindow((WindowPtr) w, (int) p.h, (int) p.v, (Boolean) 0);
				/* Move the window we're placeing there */
		}

		ShowCursor(); /* Restore the saved state of the WMgrPort */
		HidePen();
		SetPenState(&pnState);
		SetClip(clipRgn);
		DisposHandle(clipRgn); /* Don't eat space, either... */

		SetPort(oldPort);
	} else {
		SysBeep(30); /* Here we beep if an application tries to show an
						already visible window. Good for tracking unnecessary
						ShowWindows */
	}

	asm {
			move.l	@moof, a0 /* The loader looks for "Moof!", and stores
								the place to jump to there */
			unlk	a6 /* ONLY if you use local variables ! */
			jmp		(a0) /* JMP, not JSR. This is not a tail patch, and thus,
							shouldn't break any OS patch */
	}
}

{ ---------------- Set window tpat Color -------------------- }

/*
	SetWindow tpat

	This trap patch will make ShowWindow act like X-windows,
	so you may place a new window wherever you like.

*/

/* lo-mem globals that are documented, somewhere... */
extern  Point   MTemp           :   0x828;
extern  Point   RawMouse        :   0x82c;
extern  int     CrsrNewCouple   :   0x8ce;

main(CWindowPtr w)
{
	char blackPat[8];
	RGBColor savedC;

	int ofx = w->portRect.right - w->portRect.left,
		ofy = w->portRect.bottom - w->portRect.top;
	int x;

	asm {
			bra		@done
	moof:	dc		'Mo', 'of', '!\000'
	done:	nop
	}

/* Do the stuff here ! */

	for(x=0; x < 8; x++) blackPat[x] = 0x55 << (x & 1);
	if(!(((WindowPeek) w)->visible)) {
		Point p;
		
		GrafPtr oldPort;
		GrafPtr myPort;
		RGBColor c = { 0, 0, 0 };
		PenState pnState;
		RgnHandle clipRgn = NewRgn();

		GetPort(&oldPort);
		GetCWMgrPort(&myPort);
		SetPort(myPort);
		GetForeColor(&savedC);

		GetClip(clipRgn);
		SetClip(GetGrayRgn());

		if((w->portVersion & 0xE000) == 0) { /* If an old-style graf port */
			p = * (Point *) &(((GrafPtr) w)->portBits.bounds);
		} else {
			p = * (Point *) &((*(w->portPixMap))->bounds);
		}
		p.h = -p.h;
		p.v = -p.v;
	    RawMouse = p;
    	MTemp = p;
    	CrsrNewCouple = 0xffff;

		do {
			long l;
			Delay(2, &l);
		} while(Button());

		GetPenState(&pnState);
		HideCursor();
		ShowPen();
		PenSize(2, 2);
		RGBForeColor(&c);
		PenMode(patXor);
		PenPat(blackPat);

		while(!Button()) {
			Rect r;
			long l;

			GetMouse((Point *) &r);
			r.bottom = r.top + ofy;
			r.right = r.left + ofx;
			FrameRect(&r);
			Delay(2, &l);
			FrameRect(&r);
		};

		{
			GetMouse(&p);
			LocalToGlobal(&p);
			MoveWindow((WindowPtr) w, (int) p.h, (int) p.v, (Boolean) 0);
		}

		ShowCursor();
		HidePen();
		SetPenState(&pnState);
		SetClip(clipRgn);
		DisposHandle(clipRgn);
		RGBForeColor(&savedC);

		SetPort(oldPort);
	} else {
		SysBeep(30);
	}

	asm {
			move.l	@moof, a0
			unlk	a6 /* ONLY if you use local variables ! */
			jmp		(a0)
	}
}


New Volume Scanning Algorithm

By John Norstad

In Tech Note #68, "Searching All Directories on an HFS Volume", Apple gives a very simple algorithm for disk scanning. There's a problem with this algorithm, however, which I discovered while working on my anti-virus program Disinfectant. I've come up with an improved algorithm that solves the problem. The new algorithm will be part of Disinfectant version 1.1, which we hope to release early next week.

I've wanted to "publish" this new algorithm so that everyone can benefit from it. comp.sys.mac.programmer seems as good a place as any!

Please understand that this problem is not a "bug" in Disinfectant 1.0, despite what MacWeek has to say :-) The "bug" is shared by any program which uses the TN 68 algorithm to do disk scanning, which I suspect is all programs which do disk scanning.

The basic idea outlined in Tech Note #68 is to make indexed calls to the PBGetCatInfo file manager routine. We'll use (abuse) the following notation for these calls:

   r = PBGetCatInfo(d, i, o)

means "call PBGetCatInfo to get the i'th object o in directory d, with result code r." Note that r will be non-zero if there are no more objects in the directory.

The algorithm in TN 68, expressed in pseudo-c and stripped of all the bells and whistles, is as follows:

   i = 1
   while (true) {
      if (PBGetCatInfo(d, i, o)) break
      if o is a subdirectory call ourselves recursively to scan o
      if o is a file scan it
      i++
   }

This algorithm seems quite simple and fool-proof at first glance, but it only works if you assume that no other users or tasks are creating or deleting files or directories while the scan is in progress.

As an extreme example, suppose we're scanning a server volume that contains two files named A and B and a directory C that contains another 1000 files. Suppose that while we're scanning file B some other user deletes file A. Our index i in the above algorithm is 2 while we're scanning file B. When we finish scanning file B we increment i to 3 and loop, calling PBGetCatInfo to get the third object in the directory. But there are now only two objects in the directory (B and C), so the PBGetCatInfo call returns a non-zero result code and we break out of the loop and quit. The net result is that we end up scanning only 2 out of the 1002 total files on the server!

This problem is most serious when scanning server volumes, where the probability of other users creating or deleting objects is often significant. The problem can also occur on local volumes under MultiFinder if other tasks are creating or deleting objects during a scan, or if our program itself creates or deletes objects on the volume during the scan. (Disinfectant 1.0 suffers from all three problems, but only the server problem is really serious.)

My solution is quite simple. I simply recall PBGetCatInfo immediately after scanning an object to see if it has changed its position in the directory. If the position has changed, I rescan the directory to attempt to locate the new position.

The revised algorithm is:

   i = 1
   while (true) {     
      if (PBGetCatInfo(d, i, o)) break
      if o is a subdirectory call ourselves recursively to scan o
      if o is a file scan it
      n = the name of object o
      if (!PBGetCatInfo(d, i, o)) {  /* recall PBGetCatInfo */
         m = the name of object o
         if (n == m) {              /* usual case - no position change */
            i++                     /* continue scan with next object */
            continue
         }
      }
      oldi = i                     /* save our old location */
      i = 1                        /* start looking for our new location */
      while (true) {
         if (PBGetCatInfo(d, i, o)) {
            i = oldi               /* just in case we've been deleted in
            break                  /* the last few milliseconds */
         }
         m = the name of object o
         if (n == m) {             /* found new location */
            i++                    /* continue scan with next object */
            break
         }
         i++
      }
   }

There is still an unavoidable window in this algorithm where our PBGetCatInfo indices can get out of synch with reality, but it is now only milliseconds wide instead of seconds or even minutes wide. So the new algorithm is still not perfect, but it's orders of magnitude better than the old naive one.

In my first attempt to design this new algorithm I tried to be fancy - I didn't rescan from the beginning of the directory, but I instead tried to scan backwards or forwards from the current position. This technique was slightly faster, but assumed that the directory was maintained in alphabetical order using the RelString toolbox routine with caseSens=false and diacSens=true. This works OK on normal volumes, but with foreign file systems and in other "non-standard" cases we can't assume that directories are in any particular order. The final algorithm presented above does not depend on directories being maintained in any particular order.

Please note that my new algorithm hasn't yet been put to the acid test of use by millions of real live users. But I think it's reasonable and it has worked just fine in my tests. Apple, of course, knows nothing about all this. If they did they'd probably tell me that it would break in system 7.0 :-) So use it at your own risk, etc., etc.

It's interesting that this problem is not shared by UNIX and other operating systems. In UNIX once an entry is made in a directory its position never changes. When entries are deleted they're simply marked "unused". The system does not attempt to move all the following entries down to close up the hole. There is no attempt made to keep the directories in any particular order.

The new algorithm is part of the reusable module scan.c, which is part of the "public" source code of Disinfectant. Write to me at the address below if you'd like a copy.

Please excuse the length of this posting. I thought this was a nifty trick, and there might be others who will find it useful.

John Norstad

Academic Computing and Network Services

Northwestern University

Bitnet: jln@nuacc

Internet: jln@acns.nwu.edu

AppleLink: a0173

CompuServe: 76666,573