Windows2000/private/ntos/w32/ntuser/rtl/alignrec.c

665 lines
19 KiB
C
Raw Normal View History

2001-01-01 00:00:00 +01:00
/*
* Module Name: mergerec.c
* Contains all the code to reposition rectangles
* Copyright (c) 1985 - 1999, Microsoft Corporation
* NOTES:
* History:
*/
#include "precomp.h"
#pragma hdrstop
#define MONITORS_MAX 10
#define RectCenterX(prc) ((prc)->left+((prc)->right-(prc)->left)/2)
#define RectCenterY(prc) ((prc)->top+((prc)->bottom-(prc)->top)/2)
// ----------------------------------------------------------------------------
// INTERSECTION_AXIS()
// This macro tells us how a particular set of overlapping rectangles
// should be adjusted to remove the overlap. It is basically a condensed
// version of a lookup table that does the same job. The parameters for the
// macro are two rectangles, where one is the intersection of the other with
// a third (unspecified) rectangle. The macro compares the edges of the
// rectangles to determine which sides of the intersection were "caused" by
// the source rectangle. In the pre-condensed version of this macro, the
// results of these comparisons (4 bits) would be used to index into a 16
// entry table which specifies the way to resolve the overlap. However, this
// is highly redundant, as the table would actually represents several rotated
// and/or inverted instances of a few basic relationships:
// Horizontal Vertical Diagonal Contained Crossing
// *--* *-----* *---* *-----* *----*
// *--+* | | *-* | | *-+-* | *-* | *-+----+-*
// | || | *-+-+-* | | | | | | | | and | | | |
// *--+* | | | *-+-* | | *-* | *-+----+-*
// *--* *-* *---* *-----* *----*
// What we are really interested in determining is whether we "should" move
// the rectangles horizontally or vertically to resolve the overlap, hence we
// are testing for three states: Horizontal, Vertical and Don't Know.
// The macro gives us these three states by XORing the high and low bits of
// of the comparison to reduce the table to 4 cases where 1 and 2 are
// vertical and horizontal respectively, and then subtracting 1 so that the
// 2 bit signifies "unknown-ness."
// Note that there are some one-off cases in the comparisons because we are
// not actually looking at the third rectangle. However this greatly reduces
// the complexity so these small errors are acceptible given the scale of the
// rectangles we are comparing.
// ----------------------------------------------------------------------------
#define INTERSECTION_AXIS(a, b) \
(((((a->left == b->left) << 1) | (a->top == b->top)) ^ \
(((a->right == b->right) << 1) | (a->bottom == b->bottom))) - 1)
#define INTERSECTION_AXIS_VERTICAL (0)
#define INTERSECTION_AXIS_HORIZONTAL (1)
#define INTERSECTION_AXIS_UNKNOWN(code) (code & 2)
// ----------------------------------------------------------------------------
// CenterRectangles()
// Move all the rectangles so their origin is the center of their union.
// ----------------------------------------------------------------------------
void NEAR PASCAL CenterRectangles(LPRECT arc, UINT count)
{
LPRECT lprc, lprcL;
RECT rcUnion;
CopyRect(&rcUnion, arc);
lprcL = arc + count;
for (lprc = arc + 1; lprc < lprcL; lprc++)
{
UnionRect(&rcUnion, &rcUnion, lprc);
}
for (lprc = arc; count; count--)
{
OffsetRect(lprc, -RectCenterX(&rcUnion), -RectCenterY(&rcUnion));
lprc++;
}
}
// ----------------------------------------------------------------------------
// RemoveOverlap()
// This is called from RemoveOverlaps to resolve conflicts when two
// rectangles overlap. It returns the PMONITOR for the monitor it decided to
// move. This routine always moves rectangles away from the origin so it can
// be used to converge on a zero-overlap configuration.
// This function will bias slightly toward moving lprc2 (all other things
// being equal).
// ----------------------------------------------------------------------------
LPRECT NEAR PASCAL RemoveOverlap(LPRECT lprc1, LPRECT lprc2, LPRECT lprcI)
{
LPRECT lprcMove, lprcStay;
POINT ptC1, ptC2;
BOOL fNegative;
BOOL fC1Neg;
BOOL fC2Neg;
int dC1, dC2;
int xOffset;
int yOffset;
int nAxis;
// Compute the centers of both rectangles. We will need them later.
ptC1.x = RectCenterX(lprc1);
ptC1.y = RectCenterY(lprc1);
ptC2.x = RectCenterX(lprc2);
ptC2.y = RectCenterY(lprc2);
// Decide whether we should move things horizontally or vertically. All
// this goop is here so it will "feel" right when the system needs to
// move a monitor on you.
nAxis = INTERSECTION_AXIS(lprcI, lprc1);
if (INTERSECTION_AXIS_UNKNOWN(nAxis))
{
// Is this a "big" intersection between the two rectangles?
if (PtInRect(lprcI, ptC1) || PtInRect(lprcI, ptC2))
{
// This is a "big" overlap. Decide if the rectangles
// are aligned more "horizontal-ish" or "vertical-ish."
xOffset = ptC1.x - ptC2.x;
if (xOffset < 0)
xOffset *= -1;
yOffset = ptC1.y - ptC2.y;
if (yOffset < 0)
yOffset *= -1;
if (xOffset >= yOffset)
nAxis = INTERSECTION_AXIS_HORIZONTAL;
else
nAxis = INTERSECTION_AXIS_VERTICAL;
}
else
{
// This is a "small" overlap. Move the rectangles the
// smallest distance that will fix the overlap.
if ((lprcI->right - lprcI->left) <= (lprcI->bottom - lprcI->top))
nAxis = INTERSECTION_AXIS_HORIZONTAL;
else
nAxis = INTERSECTION_AXIS_VERTICAL;
}
}
// We now need to pick the rectangle to move. Move the one
// that is further from the origin along the axis of motion.
if (nAxis == INTERSECTION_AXIS_HORIZONTAL)
{
dC1 = ptC1.x;
dC2 = ptC2.x;
}
else
{
dC1 = ptC1.y;
dC2 = ptC2.y;
}
if ((fC1Neg = (dC1 < 0)) != 0)
dC1 *= -1;
if ((fC2Neg = (dC2 < 0)) != 0)
dC2 *= -1;
if (dC2 < dC1)
{
lprcMove = lprc1;
lprcStay = lprc2;
fNegative = fC1Neg;
}
else
{
lprcMove = lprc2;
lprcStay = lprc1;
fNegative = fC2Neg;
}
// Compute a new home for the rectangle and put it there.
if (nAxis == INTERSECTION_AXIS_HORIZONTAL)
{
int xPos;
if (fNegative)
xPos = lprcStay->left - (lprcMove->right - lprcMove->left);
else
xPos = lprcStay->right;
xOffset = xPos - lprcMove->left;
yOffset = 0;
}
else
{
int yPos;
if (fNegative)
yPos = lprcStay->top - (lprcMove->bottom - lprcMove->top);
else
yPos = lprcStay->bottom;
yOffset = yPos - lprcMove->top;
xOffset = 0;
}
OffsetRect(lprcMove, xOffset, yOffset);
return lprcMove;
}
// ----------------------------------------------------------------------------
// RemoveOverlaps()
// This is called from CleanupDesktopRectangles make sure the monitor array
// is non-overlapping.
// ----------------------------------------------------------------------------
void NEAR PASCAL RemoveOverlaps(LPRECT arc, UINT count)
{
LPRECT lprc1, lprc2, lprcL;
// Center the rectangles around a common origin. We will move them outward
// when there are conflicts so centering (a) reduces running time and
// hence (b) reduces the chances of totally mangling the positions.
CenterRectangles(arc, count);
// Now loop through the array fixing any overlaps.
lprcL = arc + count;
lprc2 = arc + 1;
ReScan:
while (lprc2 < lprcL)
{
// Scan all rectangles before this one looking for intersections.
for (lprc1 = arc; lprc1 < lprc2; lprc1++)
{
RECT rcI;
// Move one of the rectanges if there is an intersection.
if (IntersectRect(&rcI, lprc1, lprc2))
{
// Move one of the rectangles out of the way and then restart
// the scan for overlaps with that rectangle (since moving it
// may have created new overlaps).
lprc2 = RemoveOverlap(lprc1, lprc2, &rcI);
goto ReScan;
}
}
lprc2++;
}
}
// ----------------------------------------------------------------------------
// AddNextContiguousRectangle()
// This is called from RemoveGaps to find the next contiguous rectangle
// in the array. If there are no more contiguous rectangles it picks the
// closest rectangle and moves it so it is contiguous.
// ----------------------------------------------------------------------------
LPRECT FAR * NEAR PASCAL AddNextContiguousRectangle(LPRECT FAR *aprc,
LPRECT FAR *pprcSplit, UINT count)
{
LPRECT FAR *pprcL;
LPRECT FAR *pprcTest;
LPRECT FAR *pprcAxis;
LPRECT FAR *pprcDiag;
UINT dAxis = (UINT)-1;
UINT dDiag = (UINT)-1;
POINT dpAxis;
POINT dpDiag;
POINT dpMove;
pprcL = aprc + count;
for (pprcTest = aprc; pprcTest < pprcSplit; pprcTest++)
{
LPRECT lprcTest = *pprcTest;
LPRECT FAR *pprcScan;
for (pprcScan = pprcSplit; pprcScan < pprcL; pprcScan++)
{
RECT rcCheckOverlap;
LPRECT lprcScan = *pprcScan;
LPRECT FAR *pprcCheckOverlap;
LPRECT FAR *FAR *pppBest;
LPPOINT pdpBest;
UINT FAR *pdBest;
UINT dX, dY;
UINT dTotal;
// Figure out how far the rectangle may be along both axes.
// Note some of these numbers could be garbage at this point but
// the code below will take care of it.
if (lprcScan->right <= lprcTest->left)
dpMove.x = dX = lprcTest->left - lprcScan->right;
else
dpMove.x = -(int)(dX = (lprcScan->left - lprcTest->right));
if (lprcScan->bottom <= lprcTest->top)
dpMove.y = dY = lprcTest->top - lprcScan->bottom;
else
dpMove.y = -(int)(dY = (lprcScan->top - lprcTest->bottom));
// Figure out whether the rectangles are vertical, horizontal or
// diagonal to each other and pick the measurements we will test.
if ((lprcScan->top < lprcTest->bottom) &&
(lprcScan->bottom > lprcTest->top))
{
// The rectangles are somewhat horizontally aligned.
dpMove.y = dY = 0;
pppBest = &pprcAxis;
pdpBest = &dpAxis;
pdBest = &dAxis;
}
else if ((lprcScan->left < lprcTest->right) &&
(lprcScan->right > lprcTest->left))
{
// The rectangles are somewhat vertically aligned.
dpMove.x = dX = 0;
pppBest = &pprcAxis;
pdpBest = &dpAxis;
pdBest = &dAxis;
}
else
{
// The rectangles are somewhat diagonally aligned.
pppBest = &pprcDiag;
pdpBest = &dpDiag;
pdBest = &dDiag;
}
// Make sure there aren't other rectangles in the way. We only
// need to check the upper array since that is the pool of
// semi-placed rectangles. Any rectangles in the lower array that
// are "in the way" will be found in a different iteration of the
// enclosing loop.
CopyRect(&rcCheckOverlap, lprcScan);
OffsetRect(&rcCheckOverlap, dpMove.x, dpMove.y);
for (pprcCheckOverlap = pprcScan + 1; pprcCheckOverlap < pprcL;
pprcCheckOverlap++)
{
RECT rc;
if (IntersectRect(&rc, *pprcCheckOverlap, &rcCheckOverlap))
break;
}
if (pprcCheckOverlap < pprcL)
{
// There was another rectangle in the way; don't use this one.
continue;
}
// If it is closer than the one we already had, use it instead.
dTotal = dX + dY;
if (dTotal < *pdBest)
{
*pdBest = dTotal;
*pdpBest = dpMove;
*pppBest = pprcScan;
}
}
}
// If we found anything along an axis use that otherwise use a diagonal.
if (dAxis != (UINT)-1)
{
pprcSplit = pprcAxis;
dpMove = dpAxis;
}
else if (dDiag != (UINT)-1)
{
// BUGBUG: consider moving the rectangle to a side in this case.
// (that, of course would add a lot of code to avoid collisions)
pprcSplit = pprcDiag;
dpMove = dpDiag;
}
else
dpMove.x = dpMove.y = 0;
// Move the monitor into place and return it as the one we chose.
if (dpMove.x || dpMove.y)
OffsetRect(*pprcSplit, dpMove.x, dpMove.y);
return pprcSplit;
}
// ----------------------------------------------------------------------------
// RemoveGaps()
// This is called from CleanupDesktopRectangles to make sure the monitor
// array is contiguous. It assumes that the array is already non-overlapping.
// ----------------------------------------------------------------------------
void NEAR PASCAL RemoveGaps(LPRECT arc, UINT count)
{
LPRECT aprc[MONITORS_MAX];
LPRECT lprc, lprcL, lprcSwap, FAR *pprc, FAR *pprcNearest;
UINT uNearest;
// We will need to find the rectangle closest to the center of the group.
// We don't really need to center the array here but it doesn't hurt and
// saves us some code below.
CenterRectangles(arc, count);
// Build an array of LPRECTs we can shuffle around with relative ease while
// not disturbing the order of the passed array. Also take note of which
// one is closest to the center so we start with it and pull the rest of
// the rectangles inward. This can make a big difference in placement when
// there are more than 2 rectangles.
uNearest = (UINT)-1;
pprc = aprc;
lprcL = (lprc = arc) + count;
while (lprc < lprcL)
{
int x, y;
UINT u;
// Fill in the array.
*pprc = lprc;
// Check if this one is closer to the center of the group.
x = RectCenterX(lprc);
y = RectCenterY(lprc);
if (x < 0) x *= -1;
if (y < 0) y *= -1;
u = (UINT)x + (UINT)y;
if (u < uNearest)
{
uNearest = u;
pprcNearest = pprc;
}
pprc++;
lprc++;
}
// Now make sure we move everything toward the centermost rectangle.
if (pprcNearest != aprc)
{
lprcSwap = *pprcNearest;
*pprcNearest = *aprc;
*aprc = lprcSwap;
}
// Finally, loop through the array closing any gaps.
pprc = aprc + 1;
for (lprc = arc + 1; lprc < lprcL; pprc++, lprc++)
{
// Find the next suitable rectangle to combine into the group and move
// it into position.
pprcNearest = AddNextContiguousRectangle(aprc, pprc, count);
// If the rectangle that was added is not the next in our array, swap.
if (pprcNearest != pprc)
{
lprcSwap = *pprcNearest;
*pprcNearest = *pprc;
*pprc = lprcSwap;
}
}
}
// ----------------------------------------------------------------------------
// CleanUpDesktopRectangles()
// This is called by CleanUpMonitorRectangles (etc) to force a set of
// rectangles into a contiguous, non-overlapping arrangement.
// ----------------------------------------------------------------------------
BOOL
AlignRects(LPRECT arc, DWORD cCount, DWORD iPrimary, DWORD dwFlags)
{
LPRECT lprc, lprcL;
// Limit for loops.
lprcL = arc + cCount;
// We don't need to get all worked up if there is only one rectangle.
if (cCount > MONITORS_MAX)
{
return FALSE;
}
if (cCount > 1)
{
if (!(dwFlags & CUDR_NOSNAPTOGRID))
{
// Align monitors on 8 pixel boundaries so GDI can use the same
// brush realization on compatible devices (BIG performance win).
// Note that we assume the size of a monitor will be in multiples
// of 8 pixels on X and Y. We cannot do this for the work areas so
// we convert them to be relative to the origins of their monitors
// for the time being.
// The way we do this alignment is to just do the overlap/gap
// resoluton in 8 pixel space (ie divide everything by 8 beforehand
// and multiply it by 8 afterward).
// Note: WE CAN'T USE MULTDIV HERE because it introduces one-off
// errors when monitors span the origin. These become eight-off
// errors when we scale things back up and we end up trying to
// create DCs with sizes like 632x472 etc (not too good). It also
// handles rounding the wierdly in both positive and negative space
// and we just want to snap things to a grid so we compensate for
// truncation differently here.
for (lprc = arc; lprc < lprcL; lprc++)
{
RECT rc;
int d;
CopyRect(&rc, lprc);
d = rc.right - rc.left;
if (rc.left < 0)
rc.left -= 4;
else
rc.left += 3;
rc.left /= 8;
rc.right = rc.left + (d / 8);
d = rc.bottom - rc.top;
if (rc.top < 0)
rc.top -= 4;
else
rc.top += 3;
rc.top /= 8;
rc.bottom = rc.top + (d / 8);
CopyRect(lprc, &rc);
}
}
// RemoveGaps is designed assuming that none of the rectangles that it
// is passed will overlap. Thus we cannot safely call it if we have
// skipped the call to RemoveOverlaps or it might loop forever.
if (!(dwFlags & CUDR_NORESOLVEPOSITIONS))
{
RemoveOverlaps(arc, cCount);
if (!(dwFlags & CUDR_NOCLOSEGAPS))
{
RemoveGaps(arc, cCount);
}
}
if (!(dwFlags & CUDR_NOSNAPTOGRID))
{
// Now return the monitor rectangles to pixel units this is a
// simple multiply and MultDiv doesn't offer us any code size
// advantage so (I guess that assumes a bit about the compiler,
// but...) just do it right here.
for (lprc = arc; lprc < lprcL; lprc++)
{
lprc->left *= 8;
lprc->top *= 8;
lprc->right *= 8;
lprc->bottom *= 8;
}
}
}
if (!(dwFlags & CUDR_NOPRIMARY))
{
// Reset all the coordinates based on the primaries position,
// so that it is always located at 0,0
LONG dx = -((arc + iPrimary)->left);
LONG dy = -((arc + iPrimary)->top);
for (lprc = arc; lprc < lprcL; lprc++)
{
OffsetRect(lprc, dx, dy);
}
}
return TRUE;
}