WindowPositioner.c

/*
    File:       WindowPositioner.c
 
    Contains:   Main program file for the ColorReset application
 
    Written by: Forrest Tanaka  
 
    Copyright:  Copyright © 1988-1999 by Apple Computer, Inc., All Rights Reserved.
 
                You may incorporate this Apple sample source code into your program(s) without
                restriction. This Apple sample source code has been provided "AS IS" and the
                responsibility for its operation is yours. You are not permitted to redistribute
                this Apple sample source code as "Apple sample source code" after having made
                changes. If you're going to re-distribute the source, we require that you make
                it clear in the source that the code was descended from Apple sample source
                code, but that you've made changes.
 
    Change History (most recent first):
                7/13/1999   Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
                
 
*/
 
/******************************************************************************\
* Header Files
\******************************************************************************/
 
#ifndef THINK_C
#include <Memory.h>
#include <Script.h>
#endif
 
#include <GestaltEqu.h>
#include "WindowPositioner.h"
 
 
/******************************************************************************\
* Constants
\******************************************************************************/
 
#define kAlertFactor 3 /* Denom of fraction of screen area above alert */
 
/* Collision rectangle specifications */
#define kCollInitH  0  /* Initial offset of collision rect from base rect */
#define kCollInitV  0  /* Initial offset of collision rect from base rect */
#define kCollWidth  16 /* Width of collision rectangle in pixels */
#define kCollHeight 16 /* Height of collision rectangle in pixels */
 
/* Staggered-window wrapping options */
#define kNotWrapped  0 /* Collision testing hasnÕt wrapped yet */
#define kHorzWrapped 1 /* Collision testing wrapped horizontally */
#define kVertWrapped 2 /* Collision testing wrapped vertically */
 
/* Coordinates of temporary off-screen window for calculating bias */
#define kOffScreenTop    -32008 /* Top coordinate of off-screen window */
#define kOffScreenLeft   -32008 /* Left coordinate of off-screen window */
#define kOffScreenBottom -32000 /* Bottom coordinate of off-screen window */
#define kOffScreenRight  -32000 /* Right coordinate of off-screen window */
 
#ifndef THINK_C
#define topLeft(r) (*((Point *) &(r).top))
#define botRight(r) (*((Point *) &(r).bottom))
#define screenBits qd.screenBits
#endif
 
 
/******************************************************************************\
* Prototypes
\******************************************************************************/
 
void StaggerScreenRect(
    Rect  *baseRect,
    Rect  *modWindowRect,
    short hBias,
    short vBias);
 
GDHandle GetWindowGDevice(
    WindowPtr theWindow);
 
void GetGlobalWindowRect(
    WindowPtr theWindow,
    Rect      *globalRect);
 
 
/******************************************************************************\
* Public: PositionScreenRect
*
* PositionScreenRect concerns itself with two rectangles: the base rectangle and
* the window rectangle.  The window rectangle is simply the rectangle of the
* window as passed in the windowRect parameter.  The base rectangle is what the
* window rectangle should be positioned on.  If the screenOption specifies that
* the window rectangle should be positioned based on a screen, then the base
* rectangle is the global rectangle of the screen that the window should be
* displayed on.  If the screenOption specifies that the window rectangle should
* be positioned relative to the position of another window, then the base
* rectangle is the global coordinates of the portRect of that window.
*
* Two screen options (specified by the screenOption parameter) specify that a
* window should be based on one screen or another.  Multiple screens are only
* supported on Color QuickDraw machines, so PositionScreenRect starts off by
* eliminating the parentScreenPos option on black & white QuickDraw machines by
* changing the kParentScreenPos option to the kMainScreenPos option.  Some
* companies offer multiple screens for black & white QuickDraw machines, but
* thereÕs no standard way to get at the rectangles of those other screens.
*
* The base rectangle is calculated first.  If the screen option is
* kMainScreenPos, then the base rectangle can be copied from the screenBits.
* bounds QuickDraw global.  If the screen option is kParentPos, then the base
* rectangle is a copy of the portRect of the parent window (specified by the
* parentWindow parameter) offset so that it has global coordinates.  If the
* screen option is kParentScreenPos, then the base rectangle is the rectangle of
* the screen that the parent window has most of its area on.  If the screen
* option isnÕt any of these, then the point (0,0) is returned and nothing more
* is done.
*
* After the base rectangle is calculated, then the position of the window on
* that base rectangle is calculated.  If the position option is kCenterPos, then
* the window rectangle is centered right smack in the middle of the base
* rectangle.  If the position option is kAlertPos, then the window rectangle is
* centered horizontally, but itÕs higher than the center vertically.  The amount
* higher that it should be is specified by the kAlertFactor constant.  This
* constant specifies the proportion of the vertical screen space aside from the
* space taken up by the window should appear below the window.  For now,
* kAlertFactor is 3, which means that there should be three times more screen
* space below the window than above.
\******************************************************************************/
 
void PositionScreenRect(
    Rect      *windowRect,    /* Rectangle to center */
    short     screenOption,   /* Options for screen to center against */
    short     positionOption, /* Centering options */
    WindowPtr parentWindow,   /* Pointer to parent window, if any */
    short     hBias,          /* portRect.left-strucRgn.rgnBBox.left */
    short     vBias)          /* portRect.top-strucRgn.rgnBBox.top */
{
    long     qdVersion;        /* Version of QuickDraw in use */
    Rect     baseRect;         /* Rectangle to center against */
    GDHandle maxGDevice;       /* GDevice containing most of parentWindow */
    short    vCenterFactor;    /* Ö left-over screen by this to get v coord */
    Point    upperLeft;        /* Returns position of portRect.topLeft */
    Boolean  goodScreenOption; /* True if screen option is valid */
    Boolean  hasCQD;           /* True if Color QuickDraw is available */
 
    /* Determine whether Color QuickDraw is available or not */
    (void)Gestalt( gestaltQuickdrawVersion, /*<*/&qdVersion );
    hasCQD = qdVersion >= gestalt8BitQD;
 
    /* If want parent windowÕs screen but no CQD, then assume main screen */
    if (screenOption == kParentScreenPos && !hasCQD)
        screenOption = kMainScreenPos;
 
    /* If parent window is nil but itÕs needed, then assume main screen */
    if ((screenOption == kParentPos || screenOption == kParentScreenPos) &&
            parentWindow == nil)
        screenOption = kMainScreenPos;
 
    /* Assume the specified screen option is good */
    goodScreenOption = true;
 
    /* Find the base rectangle */
    if (screenOption == kMainScreenPos)
    {
        /* Base rectangle is the main screen */
        baseRect = screenBits.bounds;
        baseRect.top += GetMBarHeight();
    }
    else if (screenOption == kParentPos)
        /* Base rectangle is the parent windowÕs portRect */
        GetGlobalWindowRect( parentWindow, /*<*/&baseRect );
    else if (screenOption == kParentScreenPos)
    {
        /* Base rectangle is screen containing most of parent window */
        maxGDevice = GetWindowGDevice( parentWindow );
        baseRect = (**maxGDevice).gdRect;
 
        /* If windowÕs GDevice is main screenÕs, then take menu bar out */
        if (maxGDevice == GetMainDevice())
            baseRect.top += GetMBarHeight();
    }
    else
    {
        /* Invalid screen option */
        goodScreenOption = false;
    }
 
    /* Calculate a proper location for the window if screen option is valid */
    if (goodScreenOption)
    {
        if (positionOption == kStaggerPos)
        {
            StaggerScreenRect( &baseRect, /*×*/windowRect, hBias, vBias );
            if (windowRect->right > baseRect.right - (kCollWidth / 2))
                windowRect->right = baseRect.right - (kCollWidth / 2);
            if (windowRect->bottom > baseRect.bottom - (kCollHeight / 2))
                windowRect->bottom = baseRect.bottom - (kCollHeight / 2);
        }
        else if (positionOption == kCenterPos || positionOption == kAlertPos)
        {
            /* Find amount to divide vertical screen area left over */
            if (positionOption == kCenterPos)
                vCenterFactor = 2;
            else if (positionOption == kAlertPos)
                vCenterFactor = kAlertFactor;
 
            /* Calc left and top coordinates of destination rectangle */
            upperLeft.h = (((baseRect.right - baseRect.left) - (windowRect->
                    right - windowRect->left)) >> 1) + baseRect.left;
            upperLeft.v = (((baseRect.bottom - baseRect.top) - (windowRect->
                    bottom - windowRect->top)) / vCenterFactor) + baseRect.top;
 
            /* Offset the window rectangle to upperLeft */
            OffsetRect( /*×*/windowRect, upperLeft.h - windowRect->left,
                    upperLeft.v - windowRect->top );
        }
    }
}
 
 
/******************************************************************************\
* Private: StaggerScreenRect - Find staggered position for a window
*
* This routine returns a point that, if applied to the top-left corner of the
* rectangle specified by windowRect, puts windowRect into staggered position
* within the rectangle specified by baseRect.  baseRect, windowRect, and the
* returned point are all assumed to be in global (screen) coordinates.
*
* ÒStaggered positionÓ means that an attempt is made to position windowRect such
* that its top-left corner isnÕt close to the top-left corner of an existing
* windowÕs frame.  To do this, a Òcollision rectangleÓ located near the top-left
* corner of baseRect is created and placed into the collRect local variable.
* The window list is searched to see if the top-left corner of any window frames
* are in the collision rectangle, which would constitute a collision.
* Initially, no collisions are tolerated.  If even one colliding window is
* found, then the collision rectangle is moved down and to the right and the
* window list is searched for collisions again.  If a collision is again found,
* then the collision rectangle is moved down and to the right again and so
* forth.
*
* A working copy of windowRect (StaggerScreenRect doesnÕt modify windowRect)
* in the local variable, workWindowRect, follows collRect such that itÕs top-
* left corner is at the center of collRect.  As collRect is offset after a
* collision is found, workWindowRect is offset by the same amount.
*
* If any part of workWindowRect falls outside of baseRect, then collRect and
* workWindowRect wrap around to the side of baseRect opposite to the side that
* workWindowRect fell off of, and collision-testing resumes there.  This
* wrapping is only allowed in one direction though.  For example, if
* workWindowRect falls off of the right edge of baseRect, then it and collRect
* wrap around to the left side of baseRect at the same vertical position.  If
* the bottom edge of workWindowRect subsequently falls off of the bottom edge of
* baseRect, then it wonÕt wrap around to the top because horizontally wrapping
* has already happened.  Instead, collRect and workWindowRect are set back into
* their initial positions at the top-left corner of baseRect.  If instead,
* workWindowRect first falls off of the bottom edge of baseRect, then it and
* collRect wrap around to the top of baseRect at the same horizontal position.
* If the right edge of workWindowRect subsequently falls off of the right edge
* of baseRect, then it wonÕt wrap around to the left because vertical wrapping
* has already happened.  Instead, collRect and workWindowRect are again set back
* into their initial positions at the top-left corner of baseRect.
*
* Of course, setting collRect and workWindowRect back to their initial positions
* guarantees a collision.  So, StaggerScreenRect becomes more tolerant of
* collisions.  After finding a collision at every attempt, it now allows one
* collision before moving on to the next position.  If every position had more
* than one collision, then StaggerScreenRect becomes even more tolerant and
* allows up to two collisions.  This process continues until a suitable position
* for workWindowRect is found.
*
* In all truth, workWindowRect isnÕt an EXACT copy of windowRect.  The value of
* the hBias parameter is added to both horizontal coordinates and the value of
* vBias is added to both vertical coordinates.  hBias should hold the number of
* pixels between the top coordinate of the portRect of the window being
* positioned and the top coordinate of the windowÕs frame.  Similarly, vBias
* should hold the number of pixels between the left coorindate of the portRect
* and the left cooordinate of the windowÕs frame.
\******************************************************************************/
 
static void StaggerScreenRect(
    Rect  *baseRect,      /* Rectangle within which to stagger window rect */
    Rect  *modWindowRect, /* Port rectangle to be staggered within baseRect */
    short hBias,          /* portRect.left-strucRgn.rgnBBox.left */
    short vBias)          /* portRect.top-strucRgn.rgnBBox.top */
{
    WindowPtr testWindow;       /* Window being tested to see if it collides */
    Rect      testWindowRect;   /* testWindowÕs window frame rectangle */
    Rect      collRect;         /* Rectangle in which to test for collisions */
    Rect      initCollRect;     /* Initial collRect at upper left of baseRect */
    Rect      workWindowRect;   /* Working copy of modWindowRect */
    short     windowRectWidth;  /* Width of modWindowRect */
    short     windowRectHeight; /* Height of modWindowRect */
    short     collCount;        /* # collisions found so far within collRect */
    short     maxCollCount;     /* Maximum allowed collisions within collRect */
    short     wrapStatus;       /* Tells how collRect has wrapped, if at all */
    Boolean   foundSlot;        /* True if found good slot for modWindowRect */
 
    /* Set up the initial collision rectangle at offset from baseRect */
    initCollRect.top = baseRect->top + kCollInitV;
    initCollRect.left = baseRect->left + kCollInitH;
    initCollRect.bottom = initCollRect.top + kCollHeight;
    initCollRect.right = initCollRect.left + kCollWidth;
 
    /* Set up the working destination rectangle */
    windowRectWidth = modWindowRect->right - modWindowRect->left;
    windowRectHeight = modWindowRect->bottom - modWindowRect->top;
    workWindowRect.top = initCollRect.top + vBias + kCollHeight / 2;
    workWindowRect.left = initCollRect.left + hBias + kCollWidth / 2;
    workWindowRect.bottom = workWindowRect.top + windowRectHeight;
    workWindowRect.right = workWindowRect.left + windowRectWidth;
 
    /* Set up initial conditions for the search */
    collRect = initCollRect;
    maxCollCount = 0;
    wrapStatus = kNotWrapped;
    foundSlot = false;
 
    /* Search for a slot until an appropriate one is found */
    while (!foundSlot)
    {
        /* See if a slot has <= maximum number of allowed collisions */
        collCount = 0;
        testWindow = FrontWindow();
        while (testWindow != nil && collCount <= maxCollCount)
        {
            /* Get the global rectangle covering entire window frame */
            testWindowRect = (**((WindowPeek)testWindow)->strucRgn).rgnBBox;
 
            /* If top left of window frame in testWindowRect, then collision */
            if (PtInRect( topLeft( testWindowRect ), &collRect ))
                collCount++;
 
            /* Go to the next window */
            testWindow = (WindowPtr)((WindowPeek) testWindow)->nextWindow;
        }
 
        /* If too many collisions, then shift collision rect to the next slot */
        if (collCount > maxCollCount)
        {
            /* Shift collision rectangle to the next slot */
            OffsetRect( /*×*/&collRect, kCollWidth / 2, kCollHeight / 2 );
            OffsetRect( /*×*/&workWindowRect, kCollWidth / 2, kCollHeight / 2 );
            if (workWindowRect.bottom > baseRect->bottom)
            {
                if (wrapStatus != kHorzWrapped)
                {
                    /* Wrap collision rect vertically */
                    collRect.top = initCollRect.top;
                    collRect.bottom = initCollRect.bottom;
                    wrapStatus = kVertWrapped;
                }
                else
                {
                    /* Wrapped horz, try from start & allow 1 more collision */
                    collRect = initCollRect;
                    maxCollCount++;
                }
 
                /* Make workWindowRect follow collRect */
                workWindowRect.top = collRect.top + vBias + kCollHeight / 2;
                workWindowRect.left = collRect.left + hBias + kCollWidth / 2;
                workWindowRect.bottom = workWindowRect.top + windowRectHeight;
                workWindowRect.right = workWindowRect.left + windowRectWidth;
            }
            if (workWindowRect.right > baseRect->right)
            {
                if (wrapStatus != kVertWrapped)
                {
                    /* Wrap collision rect horizontally */
                    collRect.left = initCollRect.left;
                    collRect.right = initCollRect.right;
                    wrapStatus = kHorzWrapped;
                }
                else
                {
                    /* Wrapped vert, try from start & allow 1 more collision */
                    collRect = initCollRect;
                    maxCollCount++;
                }
 
                /* Make workWindowRect follow collRect */
                workWindowRect.top = collRect.top + vBias + kCollHeight / 2;
                workWindowRect.left = collRect.left + hBias + kCollWidth / 2;
                workWindowRect.bottom = workWindowRect.top + windowRectHeight;
                workWindowRect.right = workWindowRect.left + windowRectWidth;
            }
        }
        else
            foundSlot = true;
    }
 
    /* Return the top-left corner of workWindowRect */
    OffsetRect( /*×*/modWindowRect, workWindowRect.left - modWindowRect->left,
            workWindowRect.top - modWindowRect->top );
}
 
 
/******************************************************************************\
* Private: GetWindowGDevice - Get GDevice that contains most of a window
*
* This routine searches through all active screen GDevices in the GDevice list
* for the GDevice of the screen that has the greatest area of intersection with
* the portRect of the window specified by theWindow.  A handle to this GDevice
* is returned.  If the portRect of theWindow is doesnÕt intersect any active
* screen, then nil is returned.
*
* This routine can only be called if Color QuickDraw is implemented because
* the Graphics Device Manager only exists on Color QuickDraw machines.
\******************************************************************************/
 
static GDHandle GetWindowGDevice(
    WindowPtr theWindow) /* Pointer to window being tested */
{
    GDHandle testGDevice; /* Handle to GDevice being tested */
    GDHandle maxGDevice;  /* GDevice with maximum intersection area */
    Rect     windowRect;  /* Rect of windowÕs portRect in global coords */
    Rect     commonRect;  /* Rect of intersection between windowRect & gdRect */
    long     maxArea;     /* Max area of GDevice/portRect intersection found */
    long     commonArea;  /* Area of intersection between windowRect & gdRect */
 
    /* gdRects in global coords, so get windowÕs portRect in global coords */
    GetGlobalWindowRect( theWindow, /*<*/&windowRect );
 
    /* Loop through all active screen GDevices */
    testGDevice = GetDeviceList();
    maxArea = 0;
    maxGDevice = nil;
    while (testGDevice != nil)
        {
        /* Only test if GDevice is active screen GDevice */
        if (TestDeviceAttribute( testGDevice, screenDevice ) &&
                TestDeviceAttribute ( testGDevice, screenActive ))
            /* Only check area if window and gdRect intersect */
            if (SectRect( &(**testGDevice).gdRect, &windowRect,
                    /*<*/&commonRect ))
            {
                /* Find area common to GDevice.gdRect and windowÕs portRect */
                commonArea = (long)(commonRect.right - commonRect.left) *
                        (commonRect.bottom - commonRect.top);
 
                /* If area > max area found, then update maxArea & maxGDevice */
                if (commonArea > maxArea)
                {
                    maxArea = commonArea;
                    maxGDevice = testGDevice;
                }
            }
 
        /* Try the next GDevice */
        testGDevice = GetNextDevice( testGDevice );
    }
 
    return maxGDevice;
}
 
 
/******************************************************************************\
* Private: GetGlobalWindowRect - Get a windowÕs portRect in global coordinates
*
* The portRect of the window specified by theWindow is converted from the
* windowÕs local coordinates to global (screen) coordinates.  This converted
* rectangle is returned in globalRect.  If theWindow is nil, then globalRect
* returns the rectangle [T:0 L:0 B:0 R:0].
\******************************************************************************/
 
static void GetGlobalWindowRect(
    WindowPtr theWindow,   /* Pointer to window whose global rect we want */
    Rect      *globalRect) /* Returns theWindowÕs portRect in global coords */
{
    GrafPtr savedPort; /* Pointer to current GrafPort; for restoring */
 
    if (theWindow != nil)
    {
        GetPort( /*<*/&savedPort );
        SetPort( theWindow );
        *globalRect = theWindow->portRect;
        LocalToGlobal( /*×*/&topLeft( *globalRect ) );
        LocalToGlobal( /*×*/&botRight( *globalRect ) );
        SetPort( savedPort );
    }
    else
        SetRect( /*<*/globalRect, 0, 0, 0, 0 );
}
 
/******************************************************************************\
* Public: CalcWindowBias
*
* The only reliable way to calculate the bias of a window is actually to create
* a visible window and measure the resulting thickness of the window frame, and
* then to dispose of the window immediately after the measurement is taken.  To
* avoid visual threats against good taste, the window is created far outside of
* any possible screen position, and itÕs created behind any other windows to
* avoid deactivating existing windows.
*
* To avoid as much heap disruption as possible, the window record is pre-
* allocated as a handle, which is then locked.  Then, NewWindow is called to
* create the ephemeral window.  The bias is then calculated by subtracting the
* left and top coordinates of the structure region from the left and top
* coordinates of the content region.
\******************************************************************************/
 
Point CalcWindowBias(
    short   procID,     /* Defproc ID of window whose bias is being calculated */
    Boolean goAwayFlag) /* True if window has a go-away box */
{
    Point      bias;        /* Calculated bias */
    WindowPeek biasWindow;  /* Pointer to temp window used for bias calc */
    Handle     windowStore; /* Handle to window record storage (kept locked) */
    Rect       windowRect;  /* Rectangle of window in global coords */
    GrafPtr    savedPort;   /* Pointer to current GrafPort for restoring */
 
    /* In case an error happens, default to zero bias */
    bias.h = bias.v = 0;
 
    /* Allocate WindowRecord as relocatable to avoid cluttering heap */
    windowStore = NewHandle( sizeof (WindowRecord) );
    if (windowStore != nil)
    {
        GetPort( /*<*/&savedPort );
 
        /* WindowRecord must be locked */
        HLock( windowStore );
 
        /* Calc rectangle thatÕs almost certain to be off any screen */
        SetRect( /*<*/&windowRect, kOffScreenLeft, kOffScreenTop,
                kOffScreenRight, kOffScreenBottom );
 
        /* Create visible temporary window "behind" all existing windows */
        biasWindow = (WindowPeek)NewWindow( *windowStore, &windowRect, "\p",
                true, procID, nil, goAwayFlag, 0L );
        if (biasWindow != nil)
        {
            /* Bias is content region top-left - structure region top-left */
            bias.h = (**biasWindow->contRgn).rgnBBox.left - (**biasWindow->
                    strucRgn).rgnBBox.left;
            bias.v = (**biasWindow->contRgn).rgnBBox.top - (**biasWindow->
                    strucRgn).rgnBBox.top;
        }
 
        /* Pretend this never happened */
        SetPort( savedPort );
        CloseWindow( (WindowPtr)biasWindow );
        DisposeHandle( windowStore );
    }
 
    return bias;
}