[mod.mac.sources] ZoomIdle1.1

dubois@uwmacc.UUCP (Paul DuBois) (07/26/86)

Here is the Rascal source code for ZoomIdle1.1
--- cut ---
Program ZoomIdle;

(*
    ZoomIdle
    Version 1.1     25 July 1986

    Screen saver desk accessory.  Generates successive randomly chosen
    rectangles, and zooms each into the next by interpolating a series
    of rectangles intermediate in shape and location.  Click and hold in
    menu bar to pause the display.  Click below menu bar to terminate.

    To turn into a desk accessory:

    Compile and link ZoomIdle1.1.src.  Execute DeskMaker (from within
    Rascal).   Choose "Object -> DA" from the "Make" menu.   When you
    get the configuration dialog, change the window type to -1 (no
    window).

    During testing from within Rascal, the statement that sets the
    window's windowKind field should be taken out.

    Changes since 1.0:

    Works with Macintosh XL (wider screen).
    Doesn't clear the menu bar until the window receives an activate
    event.  1.0 cleared the bar immediately, which was too early -
    some programs would crash when ZoomIdle exited.


    Paul DuBois
    Wisconsin Regional Primate Research Center
    1220 Capitol Court
    University of Wisconsin-Madison
    Madison, WI  53706

    UUCP: {allegra, ihnp4, seismo}!uwvax!uwmacc!dubois
    ARPA: dubois@easter
          dubois@unix.macc.wisc.edu
*)


Uses
    __QuickDraw
    __ToolTraps
    __OSTraps
    __DeskLib
    (*$U+*)
    uToolIntf;      (* includes uQuickDraw *)

Link
    __OSTraps
    __DeskLib
    : ;

EventMask $102;        (* mouse-down + activate *)


(*
    zoomSteps controls the number of rectangles that are drawn in each
    interpolative series.  zoomShow controls how many rectangles are
    visible at once.  These two values may be varied independently, but
    they must both be greater than zero.
*)

Const
    zoomSteps = 15;
    zoomShow = 15;


Var
    thePort:    WindowPtr;
    theMenuBar: Handle;
    zRect:      Rect[zoomShow];
    zIndex:     Integer;
    srcRect:    Rect;
    zoomAway:   Boolean;
    scrnSizeX:  Integer;    (* size of screen in pixels *)
    scrnSizeY:  Integer;


(*
    return integer between zero and max (inclusive).  assumes max is
    non-negative.
*)


Function Rand (max: integer): integer;
Var
    t: Register Integer;
{
    t := Random ();
    if t < 0 then t := -t;
    Rand := t % (max + 1);
};


(*
    Interpolate one rectangle smoothly into another.  Each rectangle is
    drawn twice.  Pen mode should be set to patXor, so that the first
    drawing shows the rectangle, the second time erases it.  Gray seems
    to work best for the pen pattern:  white is too stark a contrast,
    except perhaps for the very dialectic.
*)

Proc ZoomRect (r1, r2: Rect);
Var
    r1left, r1top: Register Integer;
    hDiff, vDiff, widDiff, htDiff: Integer;
    l, t: Register Integer;
    r, b: Integer;
    rWid, rHt: Integer;
    j: Register Integer;
    rp: ^Rect;
{
    r1left := r1.left;
    r1top := r1.top;
    hDiff := r2.left - r1left; (* positive if moving to right *)
    vDiff := r2.top - r1top;   (* positive if moving down *)
    rWid := r1.right - r1left;
    rHt := r1.bottom - r1top;
    widDiff := (r2.right - r2.left) - rWid;
    htDiff := (r2.bottom - r2.top) - rHt;
(*
    order of evaluation is important in the rect coordinate calculations.
    since all arithmetic is integer, you can't save time by calculating
    j/zoomSteps and using that - it'll usually be zero.
*)
    loop (, j := 1, ++j, j > zoomSteps)
    {
        ++zIndex;
        if zIndex >= zoomShow then
            zIndex := 0;
        rp := @zRect[zIndex];
        FrameRect (rp);    (* erase a rectangle *)
        l := r1left + (hDiff * j) / zoomSteps;
        t := r1top + (vDiff * j) / zoomSteps;
        r := l + rWid + (widDiff * j) / zoomSteps;
        b := t + rHt + (htDiff * j) / zoomSteps;
        SetRect (rp, l, t, r, b);
        FrameRect (rp);
    };
        
};


(*
    Set up to allow zooming.
    
    Change the visRgn of the grafPort so can draw in the menu bar area.
    Do NOT blank the screen yet, since some programs (e.g., MacWrite,
    DiskInfo) mess with the menu bar once they figure out that they're not
    the front window anymore - if the screen is blanked here, they will
    mess up the menu bar.  Must wait for the activate event to do that.
    On receipt of the activate event, clear the menu bar so that
    MenuSelect (which will be called from the main application) doesn't
    start merrily pulling down menus when the mouse is clicked in the menu
    bar.  Clicking in a cleared menu bar will cause MenuSelect to do
    nothing but retain control until the button is  released - effectively
    pausing the display.

    Initialize the rectangle array to a bunch of empty rects.  This must be
    done for the zoom algorithm to work correctly, because one rect is erased
    for every one drawn.  If they are set empty, the first time they are
    erased, nothing changes on the screen.
*)

Proc _Init ();
{
    GetWMgrPort (@thePort);
    srcRect := thePort^.portRect;
    scrnSizeX := srcRect.right;
    scrnSizeY := srcRect.bottom;
    thePort := NewWindow (nil, srcRect, "", true,
                            noGrowDocProc, -1L, true, 0L);
    RectRgn (thePort^.visRgn, srcRect);
    WindowPeek (thePort)^.windowKind := GetDARefNum ();

(*
    Initialize the rect array.  This also sets zIndex to a value
    that causes it to reset to zero on the first call to ZoomRect.
*)
    loop (, zIndex := 0, ++zIndex, zIndex >= zoomShow)
        SetRect (zRect[zIndex], 0, 0, 0, 0);

    zoomAway := false;
};


(*
    When an activate event is received, go ahead and clear the menu bar,
    paint the screen black, and start zooming.  The ValidRect is to prevent
    an update event - without it everything below the menu bar gets painted
    white - presumably this is done by the DA wrapper provided by
    DeskMaker?
    
    The test to check that the activate event is really an activate and
    not a deactivate is necessary when testing within Rascal.  Since the
    program will be treated as an application, it will get events other
    than those for just it's own window - in particular, the deactivate
    event for the previous window gets seen here.  If the check isn't made,
    this is what happens:  the deactivate comes in, the menu bar gets cleared
    the screen is painted and the pattern and mode are changed.  Then the
    activate comes in, the menu bar is cleared again (clobbering what was
    just saved!) and the screen is painted again - gray this time since the
    penpat was just changed.

    Quit on first mouse click in executor window.  Hide the window so
    all other windows will get updated.  Restore the menu bar as well.
*)

Proc _Event (theEvent: EventRecord);
Const
    activeFlag = 1;
Var
    pat: Pattern;
    A4: PtrB;
{
    case theEvent.what of
    
        activateEvt:
        {
            if theEvent.modifiers and activeFlag then
            {
                theMenuBar := GetMenuBar ();
                ClearMenuBar ();
                SetPort (thePort);
                PaintRect (srcRect (* = thePort^.portRect *));
                ValidRect (srcRect);
                PenMode (patXor);               (* do all drawing in xor *)
                PtrL (pat)^ := $aa55aa55L;      (* with gray pattern *)
                PtrL (pat + 4)^ := $aa55aa55L;
                PenPat (pat);
                zoomAway := true;   (* window active - can go ahead now *)
            };
        };

        mouseDown:
        {
            CloseWindow (thePort);      (* cause other windows to be updated *)
            SetMenuBar (theMenuBar);    (* restore menu bar *)
            DisposHandle (theMenuBar);
            DrawMenuBar ();
            Push (reg A4.L);
            Pop (A4);
            A4[-17] := 1;        (* halt program *)
        };

    end;
};

Proc _Main ();
Var
    dstRect: Rect;
    pt1, pt2: Point;
{
    if zoomAway then
    {
        SetPort (thePort);
        ObscureCursor ();               (* keep cursor hidden *)
        pt1.h := Rand (scrnSizeX);      (* generate rect, zoom to it *)
        pt1.v := Rand (scrnSizeY);
        pt2.h := Rand (scrnSizeX);
        pt2.v := Rand (scrnSizeY);
        Pt2Rect (pt1.vh, pt2.vh, dstRect);
        ZoomRect (srcRect, dstRect);
        srcRect := dstRect;
    };
};
--- end ---

dubois@uwmacc.UUCP (Paul DuBois) (07/26/86)

Here is the LightspeedC source to ZoomIdle1.1.  For printing, tab
size should be 4.
--- cut ---
/*
	ZoomIdle
	Version 1.1     25 July 1986

	Screen saver desk accessory.  Generates successive randomly chosen
	rectangles, and zooms each into the next by interpolating a series
	of rectangles intermediate in shape and location.  Click and hold in
	menu bar to pause the display.  Click below menu bar to terminate.

	The project should include MacTraps and ZoomIdle1.1.c.  The project
	type should be Desk Accessory.

	Changes since 1.0:

	Works with Macintosh XL (wider screen).
	Doesn't clear the menu bar until the window receives an activate
	event.  1.0 cleared the bar immediately, which was too early -
	some programs would crash when ZoomIdle exited.


	Paul DuBois
	Wisconsin Regional Primate Research Center
	1220 Capitol Court
	University of Wisconsin-Madison
	Madison, WI  53706

	UUCP: {allegra, ihnp4, seismo}!uwvax!uwmacc!dubois
	ARPA: dubois@easter
		  dubois@unix.macc.wisc.edu
*/


# include	<DeviceMgr.h>
# include	<WindowMgr.h>	/* includes QuickDraw.h, MacTypes.h */
# include	<EventMgr.h>
# include	<MenuMgr.h>

# define	nil			0L


/*
	zoomSteps controls the number of rectangles that are drawn in each
	interpolative series.  zoomShow controls how many rectangles are
	visible at once.  These two values may be varied independently, but
	they must both be greater than zero.
*/

# define	zoomSteps	15
# define	zoomShow	15


/*  global variables  */

Rect		zRect[zoomShow];
int			zIndex;


/*
	Return integer between zero and max (inclusive).  Assumes max is
	non-negative.
*/


Rand (max)
int	max;
{
register int	t;

	if ((t = Random ()) < 0) t = -t;
	return (t % (max + 1));
}


/*
	Interpolate one rectangle smoothly into another.  This algorithm
	assumes that it is erasing the previous series while it's
	drawing the new one.  The first time this is called, that is not
	true, which is why the init code sets all the zRect rectangles
	empty - it's harmless to erase empty rectangles (doesn't show on
	screen).
	
	Pen mode should be set to patXor, so that the first drawing shows
	the rectangle, the second time erases it.  Gray seems to work
	best for the pen pattern:  white is too stark a contrast, except
	perhaps for the very dialectic.
*/

ZoomRect (r1, r2)
Rect	r1, r2;

{
register int	r1left, r1top;
register int	l, t;
register int	j;
int				hDiff, vDiff, widDiff, htDiff;
int				r, b;
int				rWid, rHt;
register Rect	*rp;

	r1left = r1.left;
	r1top = r1.top;
	hDiff = r2.left - r1left;	/* positive if moving to right */
	vDiff = r2.top - r1top;		/* positive if moving down */
	rWid = r1.right - r1left;
	rHt = r1.bottom - r1top;
	widDiff = (r2.right - r2.left) - rWid;
	htDiff = (r2.bottom - r2.top) - rHt;

/*
	order of evaluation is important in the rect coordinate calculations.
	since all arithmetic is integer, you can't save time by calculating
	j/zoomSteps and using that - it'll usually be zero.
*/

	for (j = 1; j <= zoomSteps; j++)
	{
		if (++zIndex >= zoomShow)
			zIndex = 0;
		rp = &zRect[zIndex];
		FrameRect (rp);				/* erase a rectangle */
		l = r1left + (hDiff * j) / zoomSteps;
		t = r1top + (vDiff * j) / zoomSteps;
		r = l + rWid + (widDiff * j) / zoomSteps;
		b = t + rHt + (htDiff * j) / zoomSteps;
		SetRect (rp, l, t, r, b);
		FrameRect (rp);
	}
		
}


main(p, d, n)
cntrlParam *p;	/*  ==> parameter block  */
DCtlPtr d;		/*  ==> device control entry  */
int n;			/*  entry point selector  */

{
register DCtlPtr	dce = d;
Rect				dstRect;
Point				pt1, pt2;
long				pat[2];
static int			scrnSizeX;
static int			scrnSizeY;
static Rect			srcRect;
static Handle		theMenuBar;
static WindowPtr	theWind;
static Boolean		zoomAway = false;


	/*  check to make sure our data area was allocated  */
		
	if (dce->dCtlStorage == 0)
	{
		if (n == 0)						/*  open  */
			CloseDriver(dce->dCtlRefNum);
	}
	else switch (n)	/*  dispatch  */
	{
		case 0:		/*  open  */

			dce->dCtlFlags |= dNeedLock | dNeedTime;
			dce->dCtlDelay = 0;
			dce->dCtlEMask = everyEvent;
		
			GetWMgrPort (&theWind);
			srcRect = theWind->portRect;
			scrnSizeX = srcRect.right;	/* get size of screen */
			scrnSizeY = srcRect.bottom;
			theWind = NewWindow (nil, &srcRect, "\p", true,
									noGrowDocProc, -1L, true, 0L);
			RectRgn (theWind->visRgn, &srcRect);
			((WindowPeek) theWind)->windowKind = dce->dCtlRefNum;
		
/*
			Initialize the rect array.  This also sets zIndex
			to a value that causes it to reset to zero on the
			first call to ZoomRect.
*/
			for (zIndex = 0; zIndex < zoomShow; ++zIndex)
				SetRect (&zRect[zIndex], 0, 0, 0, 0);

			break;

		case 2:		/*  control  */

			SetPort (theWind);
			switch (p->csCode)
			{
				case accEvent:	/* put glasses on to read next switch */

					switch (((EventRecord *) * (long *) &p->csParam)->what)
					{
						case activateEvt:

							theMenuBar = GetMenuBar ();
							ClearMenuBar ();
							PaintRect (&srcRect /* = &theWind->portRect */);
							PenMode (patXor);	/* do all drawing in xor */
							pat[0] = pat[1] = 0xaa55aa55L;
							PenPat (&pat);	/* can't use QD global pats! */
							zoomAway = true;
							break;

						case mouseDown:	/* quit on mousedown event */

							CloseDriver(dce->dCtlRefNum);
							break;
					}
					break;

				case accRun:

					if (zoomAway)	/* activated */
					{
						ObscureCursor ();	/* keep cursor hidden */
						pt1.h = Rand (scrnSizeX);	/* generate rect and zoom to it */
						pt1.v = Rand (scrnSizeY);
						pt2.h = Rand (scrnSizeX);
						pt2.v = Rand (scrnSizeY);
						Pt2Rect (pt1, pt2, &dstRect);
						ZoomRect (srcRect, dstRect);
						srcRect = dstRect;
					}
					break;
			}
			break;

		case 4:		/*  close  */

			DisposeWindow (theWind);
			SetMenuBar (theMenuBar);	/* restore menu bar */
			DisposHandle (theMenuBar);
			DrawMenuBar ();
			break;
	}
	
	/*  done  */
	
	return(0);
}
--- end ---

dubois@uwmacc.UUCP (Paul DuBois) (07/26/86)

ZoomIdle Commentary

There are some assumptions inherent in the code.  These are not
always explicitly documented; I discuss them here.  This discussion
applies both to the Rascal and the LightspeedC source versions.

When ZoomIdle is opened, a window is put up over the screen.  The
portRect of the Window Manager port is used so it will work for a
screen of any size.  The visRgn of the port must be set to the entire
screen or drawing will only take place below the menu bar.  Then
ZoomIdle waits.

Two event types are processed:  activate and mouse down.  accRun
messages are also processed.  (accRun code chooses a rectangle randomly
and zooms to it; that code is not triggered until an activate event for
the window is received.)  Since the Event Manager reports window
deactivation before window activation, we know, when the activate event
arrives, that the previous active window (whatever it was) has been
deactivated and its owner did whatever was necessary to respond to
that.  ZoomIdle clears the menu bar to prevent the main application
from allowing menu item selection, paints the screen black, and sets
the state so that zooming begins (i.e., so that accRun messages aren't
ignored).

Mouse clicks in the menu bar are not reported to ZoomIdle, because
applications typically pass the click to MenuSelect to track the mouse
until the button is released.  With the menu bar cleared, MenuSelect
just waits until the mouse button is released.  The effect is simply to
pause the display while the button is down.  (In particular, ZoomIdle
can't be selected again; that's why there isn't any check in the code
to see if the driver is already open.  Normally, it's dangerous not to
make this check.)

Mouse clicks below the menu bar are reported to ZoomIdle, and cause
termination.  The window is destroyed and the menu bar is restored.
Note that there is no check to see if the menu bar actually needs
restoring.  It is reasonable to ask whether this is valid.  For
instance, if the user clicks the mouse after initiating ZoomIdle but
before the window comes active, will there be any menu bar to restore?
Yes, because ZoomIdle takes advantage of the fact that the perceptual
mechanisms of the Macintosh operating system induce a distortion of
temporal reality; events are not reported in chronological order but in
priority order.  Since activates are higher priority than mouse clicks,
no matter when the user clicks the mouse, ZoomIdle can never become
conscious of the click until after the activate event is received.
Hence, it *must* be true that the menu bar needs restoring when the
mouse click handling code is invoked.

The drawing pattern and mode for zooming are set to gray and patXor,
respectively.  ZoomIdle builds its own gray pattern because, as a DA,
it doesn't access off A5.  I don't know what that means, except that
the normal QuickDraw globals can't be used.