/* * 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; }