Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
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; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-03-12