gecko-dev/layout/generic/nsSpaceManager.cpp
kin%netscape.com 50e1a69237 Fix for bug #96736: [FLOATER]The "Top 1000 Reviewer" is overwritten by text
mozilla/layout/base/src/nsSpaceManager.cpp
  mozilla/layout/base/src/nsSpaceManager.h

    - Implemented nsSpaceManager methods PushState()
      and PopState().

  mozilla/layout/html/base/src/nsBlockReflowContext.cpp

    - Modified nsBlockReflowContext::DoReflowBlock() to
      call PushState() and PopState() to remove any
      floaters that may have been added to the SpaceManager
      during the intermediate "unconstrained" Reflow()
      call, used to calculate the max block width. This
      allows the Reflow() call that immediately follows
      it to properly place the floaters.


r=waterson@netscape.com  sr=dbaron@fas.harvard.edu
2002-08-19 04:41:57 +00:00

1435 lines
43 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: NPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Netscape Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Pierre Phaneuf <pp@ludusdesign.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the NPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the NPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsSpaceManager.h"
#include "nsPoint.h"
#include "nsRect.h"
#include "nsSize.h"
#include <stdlib.h>
#include "nsVoidArray.h"
#include "nsIFrame.h"
#include "nsString.h"
#include "nsIPresShell.h"
#include "nsMemory.h"
#ifdef DEBUG
#include "nsIFrameDebug.h"
#endif
/////////////////////////////////////////////////////////////////////////////
// BandList
PRInt32 nsSpaceManager::sCachedSpaceManagerCount = 0;
void* nsSpaceManager::sCachedSpaceManagers[NS_SPACE_MANAGER_CACHE_SIZE];
#define NSCOORD_MIN (-2147483647 - 1) /* minimum signed value */
nsSpaceManager::BandList::BandList()
: nsSpaceManager::BandRect(NSCOORD_MIN, NSCOORD_MIN, NSCOORD_MIN, NSCOORD_MIN, (nsIFrame*)nsnull)
{
PR_INIT_CLIST(this);
mNumFrames = 0;
}
void
nsSpaceManager::BandList::Clear()
{
if (!IsEmpty()) {
BandRect* bandRect = Head();
while (bandRect != this) {
BandRect* nxt = bandRect->Next();
delete bandRect;
bandRect = nxt;
}
PR_INIT_CLIST(this);
}
}
/////////////////////////////////////////////////////////////////////////////
// PresShell Arena allocate callback (for nsIntervalSet use below)
PR_STATIC_CALLBACK(void*)
PSArenaAllocCB(size_t aSize, void* aClosure)
{
void *rv;
NS_STATIC_CAST(nsIPresShell*, aClosure)->AllocateFrame(aSize, &rv);
return rv;
}
// PresShell Arena free callback (for nsIntervalSet use below)
PR_STATIC_CALLBACK(void)
PSArenaFreeCB(size_t aSize, void* aPtr, void* aClosure)
{
NS_STATIC_CAST(nsIPresShell*, aClosure)->FreeFrame(aSize, aPtr);
}
/////////////////////////////////////////////////////////////////////////////
// nsSpaceManager
MOZ_DECL_CTOR_COUNTER(nsSpaceManager)
nsSpaceManager::nsSpaceManager(nsIPresShell* aPresShell, nsIFrame* aFrame)
: mFrame(aFrame),
mFloatDamage(PSArenaAllocCB, PSArenaFreeCB, aPresShell)
{
MOZ_COUNT_CTOR(nsSpaceManager);
mX = mY = 0;
mFrameInfoMap = nsnull;
mSavedStates = nsnull;
}
void
nsSpaceManager::ClearFrameInfo()
{
while (mFrameInfoMap) {
FrameInfo* next = mFrameInfoMap->mNext;
delete mFrameInfoMap;
mFrameInfoMap = next;
}
}
nsSpaceManager::~nsSpaceManager()
{
MOZ_COUNT_DTOR(nsSpaceManager);
mBandList.Clear();
ClearFrameInfo();
NS_ASSERTION(!mSavedStates, "states remaining on state stack");
while (mSavedStates){
SpaceManagerState *state = mSavedStates;
mSavedStates = state->mNext;
delete state;
}
}
// static
void* nsSpaceManager::operator new(size_t aSize) CPP_THROW_NEW
{
if (sCachedSpaceManagerCount > 0) {
// We have cached unused instances of this class, return a cached
// instance in stead of always creating a new one.
return sCachedSpaceManagers[--sCachedSpaceManagerCount];
}
// The cache is empty, this means we haveto create a new instance using
// the global |operator new|.
return nsMemory::Alloc(aSize);
}
void
nsSpaceManager::operator delete(void* aPtr, size_t aSize)
{
if (!aPtr)
return;
// This space manager is no longer used, if there's still room in
// the cache we'll cache this space manager, unless the layout
// module was already shut down.
if (sCachedSpaceManagerCount < NS_SPACE_MANAGER_CACHE_SIZE &&
sCachedSpaceManagerCount >= 0) {
// There's still space in the cache for more instances, put this
// instance in the cache in stead of deleting it.
sCachedSpaceManagers[sCachedSpaceManagerCount++] = aPtr;
return;
}
// The cache is full, or the layout module has been shut down,
// delete this space manager.
nsMemory::Free(aPtr);
}
/* static */
void nsSpaceManager::Shutdown()
{
// The layout module is being shut down, clean up the cache and
// disable further caching.
PRInt32 i;
for (i = 0; i < sCachedSpaceManagerCount; i++) {
void* spaceManager = sCachedSpaceManagers[i];
if (spaceManager)
nsMemory::Free(spaceManager);
}
// Disable futher caching.
sCachedSpaceManagerCount = -1;
}
PRBool
nsSpaceManager::YMost(nscoord& aYMost) const
{
PRBool result;
if (mBandList.IsEmpty()) {
aYMost = 0;
result = PR_FALSE;
} else {
BandRect* lastRect = mBandList.Tail();
aYMost = lastRect->mBottom;
result = PR_TRUE;
}
return result;
}
/**
* Internal function that returns the list of available and unavailable space
* within the band
*
* @param aBand the first rect in the band
* @param aY the y-offset in world coordinates
* @param aMaxSize the size to use to constrain the band data
* @param aAvailableBand
*/
nsresult
nsSpaceManager::GetBandAvailableSpace(const BandRect* aBand,
nscoord aY,
const nsSize& aMaxSize,
nsBandData& aBandData) const
{
nscoord topOfBand = aBand->mTop;
nscoord localY = aY - mY;
nscoord height = PR_MIN(aBand->mBottom - aY, aMaxSize.height);
nsBandTrapezoid* trapezoid = aBandData.mTrapezoids;
nscoord rightEdge = mX + aMaxSize.width;
// Initialize the band data
aBandData.mCount = 0;
// Skip any rectangles that are to the left of the local coordinate space
while (aBand->mTop == topOfBand) {
if (aBand->mRight > mX) {
break;
}
// Get the next rect in the band
aBand = aBand->Next();
}
// This is used to track the current x-location within the band. This is in
// world coordinates
nscoord left = mX;
// Process the remaining rectangles that are within the clip width
while ((aBand->mTop == topOfBand) && (aBand->mLeft < rightEdge)) {
// Compare the left edge of the occupied space with the current left
// coordinate
if (aBand->mLeft > left) {
// The rect is to the right of our current left coordinate, so we've
// found some available space
if (aBandData.mCount >= aBandData.mSize) {
// Not enough space in the array of trapezoids
aBandData.mCount += 2 * aBand->Length() + 2; // estimate the number needed
return NS_ERROR_FAILURE;
}
trapezoid->mState = nsBandTrapezoid::Available;
trapezoid->mFrame = nsnull;
// Assign the trapezoid a rectangular shape. The trapezoid must be in the
// local coordinate space, so convert the current left coordinate
*trapezoid = nsRect(left - mX, localY, aBand->mLeft - left, height);
// Move to the next output rect
trapezoid++;
aBandData.mCount++;
}
// The rect represents unavailable space, so add another trapezoid
if (aBandData.mCount >= aBandData.mSize) {
// Not enough space in the array of trapezoids
aBandData.mCount += 2 * aBand->Length() + 1; // estimate the number needed
return NS_ERROR_FAILURE;
}
if (1 == aBand->mNumFrames) {
trapezoid->mState = nsBandTrapezoid::Occupied;
trapezoid->mFrame = aBand->mFrame;
} else {
NS_ASSERTION(aBand->mNumFrames > 1, "unexpected frame count");
trapezoid->mState = nsBandTrapezoid::OccupiedMultiple;
trapezoid->mFrames = aBand->mFrames;
}
nscoord x = aBand->mLeft;
// The first band can straddle the clip rect
if (x < mX) {
// Clip the left edge
x = mX;
}
// Assign the trapezoid a rectangular shape. The trapezoid must be in the
// local coordinate space, so convert the rects's left coordinate
*trapezoid = nsRect(x - mX, localY, aBand->mRight - x, height);
// Move to the next output rect
trapezoid++;
aBandData.mCount++;
// Adjust our current x-location within the band
left = aBand->mRight;
// Move to the next rect within the band
aBand = aBand->Next();
}
// No more rects left in the band. If we haven't yet reached the right edge,
// then all the remaining space is available
if (left < rightEdge) {
if (aBandData.mCount >= aBandData.mSize) {
// Not enough space in the array of trapezoids
aBandData.mCount++;
return NS_ERROR_FAILURE;
}
trapezoid->mState = nsBandTrapezoid::Available;
trapezoid->mFrame = nsnull;
// Assign the trapezoid a rectangular shape. The trapezoid must be in the
// local coordinate space, so convert the current left coordinate
*trapezoid = nsRect(left - mX, localY, rightEdge - left, height);
aBandData.mCount++;
}
return NS_OK;
}
nsresult
nsSpaceManager::GetBandData(nscoord aYOffset,
const nsSize& aMaxSize,
nsBandData& aBandData) const
{
NS_PRECONDITION(aBandData.mSize >= 1, "bad band data");
nsresult result = NS_OK;
// Convert the y-offset to world coordinates
nscoord y = mY + aYOffset;
// If there are no unavailable rects or the offset is below the bottommost
// band, then all the space is available
nscoord yMost;
if (!YMost(yMost) || (y >= yMost)) {
// All the requested space is available
aBandData.mCount = 1;
aBandData.mTrapezoids[0] = nsRect(0, aYOffset, aMaxSize.width, aMaxSize.height);
aBandData.mTrapezoids[0].mState = nsBandTrapezoid::Available;
aBandData.mTrapezoids[0].mFrame = nsnull;
} else {
// Find the first band that contains the y-offset or is below the y-offset
NS_ASSERTION(!mBandList.IsEmpty(), "no bands");
BandRect* band = mBandList.Head();
aBandData.mCount = 0;
while (nsnull != band) {
if (band->mTop > y) {
// The band is below the y-offset. The area between the y-offset and
// the top of the band is available
aBandData.mCount = 1;
aBandData.mTrapezoids[0] =
nsRect(0, aYOffset, aMaxSize.width, PR_MIN(band->mTop - y, aMaxSize.height));
aBandData.mTrapezoids[0].mState = nsBandTrapezoid::Available;
aBandData.mTrapezoids[0].mFrame = nsnull;
break;
} else if (y < band->mBottom) {
// The band contains the y-offset. Return a list of available and
// unavailable rects within the band
return GetBandAvailableSpace(band, y, aMaxSize, aBandData);
} else {
// Skip to the next band
band = GetNextBand(band);
}
}
}
NS_POSTCONDITION(aBandData.mCount > 0, "unexpected band data count");
return result;
}
/**
* Skips to the start of the next band.
*
* @param aBandRect A rect within the band
* @returns The start of the next band, or nsnull of this is the last band.
*/
nsSpaceManager::BandRect*
nsSpaceManager::GetNextBand(const BandRect* aBandRect) const
{
nscoord topOfBand = aBandRect->mTop;
aBandRect = aBandRect->Next();
while (aBandRect != &mBandList) {
// Check whether this rect is part of the same band
if (aBandRect->mTop != topOfBand) {
// We found the start of the next band
return (BandRect*)aBandRect;
}
aBandRect = aBandRect->Next();
}
// No bands left
return nsnull;
}
/**
* Divides the current band into two vertically
*
* @param aBandRect the first rect in the band
* @param aBottom where to split the band. This becomes the bottom of the top
* part
*/
void
nsSpaceManager::DivideBand(BandRect* aBandRect, nscoord aBottom)
{
NS_PRECONDITION(aBottom < aBandRect->mBottom, "bad height");
nscoord topOfBand = aBandRect->mTop;
BandRect* nextBand = GetNextBand(aBandRect);
if (nsnull == nextBand) {
nextBand = (BandRect*)&mBandList;
}
while (topOfBand == aBandRect->mTop) {
// Split the band rect into two vertically
BandRect* bottomBandRect = aBandRect->SplitVertically(aBottom);
// Insert the new bottom part
nextBand->InsertBefore(bottomBandRect);
// Move to the next rect in the band
aBandRect = aBandRect->Next();
}
}
PRBool
nsSpaceManager::CanJoinBands(BandRect* aBand, BandRect* aPrevBand)
{
PRBool result;
nscoord topOfBand = aBand->mTop;
nscoord topOfPrevBand = aPrevBand->mTop;
// The bands can be joined if:
// - they're adjacent
// - they have the same number of rects
// - each rect has the same left and right edge as its corresponding rect, and
// the rects are occupied by the same frames
if (aPrevBand->mBottom == aBand->mTop) {
// Compare each of the rects in the two bands
while (PR_TRUE) {
if ((aBand->mLeft != aPrevBand->mLeft) || (aBand->mRight != aPrevBand->mRight)) {
// The rects have different edges
result = PR_FALSE;
break;
}
if (!aBand->HasSameFrameList(aPrevBand)) {
// The rects are occupied by different frames
result = PR_FALSE;
break;
}
// Move to the next rects within the bands
aBand = aBand->Next();
aPrevBand = aPrevBand->Next();
// Have we reached the end of either band?
PRBool endOfBand = aBand->mTop != topOfBand;
PRBool endOfPrevBand = aPrevBand->mTop != topOfPrevBand;
if (endOfBand || endOfPrevBand) {
result = endOfBand & endOfPrevBand;
break; // all done
}
}
} else {
// The bands aren't adjacent
result = PR_FALSE;
}
return result;
}
/**
* Tries to join the two adjacent bands. Returns PR_TRUE if successful and
* PR_FALSE otherwise
*
* If the two bands are joined, the previous band is the the band that's deleted
*/
PRBool
nsSpaceManager::JoinBands(BandRect* aBand, BandRect* aPrevBand)
{
if (CanJoinBands(aBand, aPrevBand)) {
BandRect* startOfNextBand = aBand;
while (aPrevBand != startOfNextBand) {
// Adjust the top of the band we're keeping, and then move to the next
// rect within the band
aBand->mTop = aPrevBand->mTop;
aBand = aBand->Next();
// Delete the rect from the previous band
BandRect* next = aPrevBand->Next();
aPrevBand->Remove();
delete aPrevBand;
aPrevBand = next;
}
return PR_TRUE;
}
return PR_FALSE;
}
/**
* Adds a new rect to a band.
*
* @param aBand the first rect in the band
* @param aBandRect the band rect to add to the band
*/
void
nsSpaceManager::AddRectToBand(BandRect* aBand, BandRect* aBandRect)
{
NS_PRECONDITION((aBand->mTop == aBandRect->mTop) &&
(aBand->mBottom == aBandRect->mBottom), "bad band");
NS_PRECONDITION(1 == aBandRect->mNumFrames, "shared band rect");
nscoord topOfBand = aBand->mTop;
// Figure out where in the band horizontally to insert the rect
do {
// Compare the left edge of the new rect with the left edge of the existing
// rect
if (aBandRect->mLeft < aBand->mLeft) {
// The new rect's left edge is to the left of the existing rect's left edge.
// Could be any of these cases (N is new rect, E is existing rect):
//
// Case 1: left-of Case 2: overlaps Case 3: N.contains(E)
// --------------- ---------------- ---------------------
// +-----+ +-----+ +-----+ +---------+
// | N | | E | | N | | N |
// +-----+ +-----+ +-----+ +---------+
// +-----+ +---+
// | E | | E |
// +-----+ +---+
//
// Do the two rectangles overlap?
if (aBandRect->mRight <= aBand->mLeft) {
// No, the new rect is completely to the left of the existing rect
// (case #1). Insert a new rect
aBand->InsertBefore(aBandRect);
return;
}
// Yes, they overlap. Compare the right edges.
if (aBandRect->mRight > aBand->mRight) {
// The new rect's right edge is to the right of the existing rect's
// right edge (case #3). Split the new rect
BandRect* r1 = aBandRect->SplitHorizontally(aBand->mLeft);
// Insert the part of the new rect that's to the left of the existing
// rect as a new band rect
aBand->InsertBefore(aBandRect);
// Continue below with the part that overlaps the existing rect
aBandRect = r1;
} else {
if (aBand->mRight > aBandRect->mRight) {
// The existing rect extends past the new rect (case #2). Split the
// existing rect
BandRect* r1 = aBand->SplitHorizontally(aBandRect->mRight);
// Insert the new right half of the existing rect
aBand->InsertAfter(r1);
}
// Insert the part of the new rect that's to the left of the existing
// rect
aBandRect->mRight = aBand->mLeft;
aBand->InsertBefore(aBandRect);
// Mark the existing rect as shared
aBand->AddFrame(aBandRect->mFrame);
return;
}
}
if (aBandRect->mLeft > aBand->mLeft) {
// The new rect's left edge is to the right of the existing rect's left
// edge. Could be any one of these cases:
//
// Case 4: right-of Case 5: overlaps Case 6: E.Contains(N)
// --------------- ---------------- ---------------------
// +-----+ +-----+ +-----+ +------------+
// | E | | N | | E | | E |
// +-----+ +-----+ +-----+ +------------+
// +-----+ +-----+
// | N | | N |
// +-----+ +-----+
//
if (aBandRect->mLeft >= aBand->mRight) {
// The new rect is to the right of the existing rect (case #4), so move
// to the next rect in the band
aBand = aBand->Next();
continue;
}
// The rects overlap, so divide the existing rect into two rects: the
// part to the left of the new rect, and the part that overlaps
BandRect* r1 = aBand->SplitHorizontally(aBandRect->mLeft);
// Insert the new right half of the existing rect, and make it the current
// rect
aBand->InsertAfter(r1);
aBand = r1;
}
// At this point the left edge of the new rect is the same as the left edge
// of the existing rect
NS_ASSERTION(aBandRect->mLeft == aBand->mLeft, "unexpected rect");
// Compare which rect is wider, the new rect or the existing rect
if (aBand->mRight > aBandRect->mRight) {
// The existing rect is wider (case #6). Divide the existing rect into
// two rects: the part that overlaps, and the part to the right of the
// new rect
BandRect* r1 = aBand->SplitHorizontally(aBandRect->mRight);
// Insert the new right half of the existing rect
aBand->InsertAfter(r1);
// Mark the overlap as being shared
aBand->AddFrame(aBandRect->mFrame);
return;
} else {
// Indicate the frames share the existing rect
aBand->AddFrame(aBandRect->mFrame);
if (aBand->mRight == aBandRect->mRight) {
// The new and existing rect have the same right edge. We're all done,
// and the new band rect is no longer needed
delete aBandRect;
return;
} else {
// The new rect is wider than the existing rect (cases #5). Set the
// new rect to be the overhang, and move to the next rect within the band
aBandRect->mLeft = aBand->mRight;
aBand = aBand->Next();
continue;
}
}
} while (aBand->mTop == topOfBand);
// Insert a new rect
aBand->InsertBefore(aBandRect);
}
// When comparing a rect to a band there are seven cases to consider.
// 'R' is the rect and 'B' is the band.
//
// Case 1 Case 2 Case 3 Case 4
// ------ ------ ------ ------
// +-----+ +-----+ +-----+ +-----+
// | R | | R | +-----+ +-----+ | | | |
// +-----+ +-----+ | | | R | | B | | B |
// +-----+ | B | +-----+ | | +-----+ | |
// | | | | +-----+ | R | +-----+
// | B | +-----+ +-----+
// | |
// +-----+
//
//
//
// Case 5 Case 6 Case 7
// ------ ------ ------
// +-----+ +-----+ +-----+ +-----+
// | | | R | | B | | | +-----+
// | B | +-----+ +-----+ | R | | B |
// | | | | +-----+
// +-----+ +-----+
// +-----+
// | R |
// +-----+
//
void
nsSpaceManager::InsertBandRect(BandRect* aBandRect)
{
// If there are no existing bands or this rect is below the bottommost
// band, then add a new band
nscoord yMost;
if (!YMost(yMost) || (aBandRect->mTop >= yMost)) {
mBandList.Append(aBandRect);
return;
}
// Examine each band looking for a band that intersects this rect
NS_ASSERTION(!mBandList.IsEmpty(), "no bands");
BandRect* band = mBandList.Head();
while (nsnull != band) {
// Compare the top edge of this rect with the top edge of the band
if (aBandRect->mTop < band->mTop) {
// The top edge of the rect is above the top edge of the band.
// Is there any overlap?
if (aBandRect->mBottom <= band->mTop) {
// Case #1. This rect is completely above the band, so insert a
// new band before the current band
band->InsertBefore(aBandRect);
break; // we're all done
}
// Case #2 and case #7. Divide this rect, creating a new rect for
// the part that's above the band
BandRect* bandRect1 = new BandRect(aBandRect->mLeft, aBandRect->mTop,
aBandRect->mRight, band->mTop,
aBandRect->mFrame);
// Insert bandRect1 as a new band
band->InsertBefore(bandRect1);
// Modify this rect to exclude the part above the band
aBandRect->mTop = band->mTop;
} else if (aBandRect->mTop > band->mTop) {
// The top edge of the rect is below the top edge of the band. Is there
// any overlap?
if (aBandRect->mTop >= band->mBottom) {
// Case #5. This rect is below the current band. Skip to the next band
band = GetNextBand(band);
continue;
}
// Case #3 and case #4. Divide the current band into two bands with the
// top band being the part that's above the rect
DivideBand(band, aBandRect->mTop);
// Skip to the bottom band that we just created
band = GetNextBand(band);
}
// At this point the rect and the band should have the same y-offset
NS_ASSERTION(aBandRect->mTop == band->mTop, "unexpected band");
// Is the band higher than the rect?
if (band->mBottom > aBandRect->mBottom) {
// Divide the band into two bands with the top band the same height
// as the rect
DivideBand(band, aBandRect->mBottom);
}
if (aBandRect->mBottom == band->mBottom) {
// Add the rect to the band
AddRectToBand(band, aBandRect);
break;
} else {
// Case #4 and case #7. The rect contains the band vertically. Divide
// the rect, creating a new rect for the part that overlaps the band
BandRect* bandRect1 = new BandRect(aBandRect->mLeft, aBandRect->mTop,
aBandRect->mRight, band->mBottom,
aBandRect->mFrame);
// Add bandRect1 to the band
AddRectToBand(band, bandRect1);
// Modify aBandRect to be the part below the band
aBandRect->mTop = band->mBottom;
// Continue with the next band
band = GetNextBand(band);
if (nsnull == band) {
// Append a new bottommost band
mBandList.Append(aBandRect);
break;
}
}
}
}
nsresult
nsSpaceManager::AddRectRegion(nsIFrame* aFrame, const nsRect& aUnavailableSpace)
{
NS_PRECONDITION(nsnull != aFrame, "null frame");
// See if there is already a region associated with aFrame
FrameInfo* frameInfo = GetFrameInfoFor(aFrame);
if (nsnull != frameInfo) {
NS_WARNING("aFrame is already associated with a region");
return NS_ERROR_FAILURE;
}
// Convert the frame to world coordinates
nsRect rect(aUnavailableSpace.x + mX, aUnavailableSpace.y + mY,
aUnavailableSpace.width, aUnavailableSpace.height);
// Create a frame info structure
frameInfo = CreateFrameInfo(aFrame, rect);
if (nsnull == frameInfo) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Is the rect empty?
if (aUnavailableSpace.IsEmpty()) {
// The rect doesn't consume any space, so don't add any band data
return NS_OK;
}
// Allocate a band rect
BandRect* bandRect = new BandRect(rect.x, rect.y, rect.XMost(), rect.YMost(), aFrame);
if (nsnull == bandRect) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Insert the band rect
InsertBandRect(bandRect);
return NS_OK;
}
nsresult
nsSpaceManager::ResizeRectRegion(nsIFrame* aFrame,
nscoord aDeltaWidth,
nscoord aDeltaHeight,
AffectedEdge aEdge)
{
// Get the frame info associated with with aFrame
FrameInfo* frameInfo = GetFrameInfoFor(aFrame);
if (nsnull == frameInfo) {
NS_WARNING("no region associated with aFrame");
return NS_ERROR_INVALID_ARG;
}
// Compute new rect
nsRect rect(frameInfo->mRect);
rect.SizeBy(aDeltaWidth, aDeltaHeight);
if (aEdge == LeftEdge) {
rect.x += aDeltaWidth;
}
// For the time being just remove it and add it back in. Because
// AddRectRegion() operates relative to the local coordinate space,
// translate from world coordinates to the local coordinate space
rect.MoveBy(-mX, -mY);
RemoveRegion(aFrame);
return AddRectRegion(aFrame, rect);
}
nsresult
nsSpaceManager::OffsetRegion(nsIFrame* aFrame, nscoord aDx, nscoord aDy)
{
// Get the frame info associated with with aFrame
FrameInfo* frameInfo = GetFrameInfoFor(aFrame);
if (nsnull == frameInfo) {
NS_WARNING("no region associated with aFrame");
return NS_ERROR_INVALID_ARG;
}
// Compute new rect
nsRect rect(frameInfo->mRect);
rect.MoveBy(aDx, aDy);
// For the time being just remove it and add it back in. Because
// AddRectRegion() operates relative to the local coordinate space,
// translate from world coordinates to the local coordinate space
rect.MoveBy(-mX, -mY);
RemoveRegion(aFrame);
return AddRectRegion(aFrame, rect);
}
nsresult
nsSpaceManager::RemoveRegion(nsIFrame* aFrame)
{
// Get the frame info associated with aFrame
FrameInfo* frameInfo = GetFrameInfoFor(aFrame);
if (nsnull == frameInfo) {
NS_WARNING("no region associated with aFrame");
return NS_ERROR_INVALID_ARG;
}
if (!frameInfo->mRect.IsEmpty()) {
NS_ASSERTION(!mBandList.IsEmpty(), "no bands");
BandRect* band = mBandList.Head();
BandRect* prevBand = nsnull;
PRBool prevFoundMatchingRect = PR_FALSE;
// Iterate each band looking for rects tagged with aFrame
while (nsnull != band) {
BandRect* rect = band;
BandRect* prevRect = nsnull;
nscoord topOfBand = band->mTop;
PRBool foundMatchingRect = PR_FALSE;
PRBool prevIsSharedRect = PR_FALSE;
// Iterate each rect in the band
do {
PRBool isSharedRect = PR_FALSE;
if (rect->IsOccupiedBy(aFrame)) {
// Remember that we found a matching rect in this band
foundMatchingRect = PR_TRUE;
if (rect->mNumFrames > 1) {
// The band rect is occupied by more than one frame
rect->RemoveFrame(aFrame);
// Remember that this rect was being shared by more than one frame
// including aFrame
isSharedRect = PR_TRUE;
} else {
// The rect isn't shared so just delete it
BandRect* next = rect->Next();
rect->Remove();
if (rect == band) {
// The rect we're deleting is the start of the band
if (topOfBand == next->mTop) {
band = next;
} else {
band = nsnull;
}
}
delete rect;
rect = next;
// We don't need to try and coalesce adjacent rects in this case
prevRect = nsnull;
prevIsSharedRect = PR_FALSE;
continue;
}
}
// If we found a shared rect occupied by aFrame, then we need to try
// and coalesce adjacent rects
if (prevIsSharedRect || (isSharedRect && (nsnull != prevRect))) {
NS_ASSERTION(nsnull != prevRect, "no previous rect");
if ((prevRect->mRight == rect->mLeft) && (prevRect->HasSameFrameList(rect))) {
// Modify the current rect's left edge, and delete the previous rect
rect->mLeft = prevRect->mLeft;
prevRect->Remove();
if (prevRect == band) {
// the rect we're deleting is the start of the band
band = rect;
}
delete prevRect;
}
}
// Get the next rect in the band
prevRect = rect;
prevIsSharedRect = isSharedRect;
rect = rect->Next();
} while (rect->mTop == topOfBand);
if (nsnull != band) {
// If we found a rect occupied by aFrame in this band or the previous band
// then try to join the two bands
if ((nsnull != prevBand) && (foundMatchingRect || prevFoundMatchingRect)) {
// Try and join this band with the previous band
JoinBands(band, prevBand);
}
}
// Move to the next band
prevFoundMatchingRect = foundMatchingRect;
prevBand = band;
band = (rect == &mBandList) ? nsnull : rect;
}
}
DestroyFrameInfo(frameInfo);
return NS_OK;
}
void
nsSpaceManager::ClearRegions()
{
ClearFrameInfo();
mBandList.Clear();
}
void
nsSpaceManager::PushState()
{
// This is a quick and dirty push implementation, which
// only saves the (x,y) and last frame in the mFrameInfoMap
// which is enough info to get us back to where we should be
// when pop is called.
//
// The alternative would be to make full copies of the contents
// of mBandList and mFrameInfoMap and restore them when pop is
// called, but I'm not sure it's worth the effort/bloat at this
// point, since this push/pop mechanism is only used to undo any
// floaters that were added during the unconstrained reflow
// in nsBlockReflowContext::DoReflowBlock(). (See bug 96736)
//
// It should also be noted that the state for mFloatDamage is
// intentionally not saved or restored in PushState() and PopState(),
// since that could lead to bugs where damage is missed/dropped when
// we move from position A to B (during the intermediate incremental
// reflow mentioned above) and then from B to C during the subsequent
// reflow. In the typical case A and C will be the same, but not always.
// Allowing mFloatDamage to accumulate the damage incurred during both
// reflows ensures that nothing gets missed.
SpaceManagerState *state = new SpaceManagerState;
NS_ASSERTION(state, "PushState() failed!");
if (!state) {
return;
}
state->mX = mX;
state->mY = mY;
if (mFrameInfoMap) {
state->mLastFrame = mFrameInfoMap->mFrame;
}
// Now that we've saved our state, add it to mSavedStates.
state->mNext = mSavedStates;
mSavedStates = state;
}
void
nsSpaceManager::PopState()
{
// This is a quick and dirty pop implementation, to
// match the current implementation of PushState(). The
// idea here is to remove any frames that have been added
// to the mFrameInfoMap since the last call to PushState().
NS_ASSERTION(mSavedStates, "Invalid call to PopState()!");
if (!mSavedStates) {
return;
}
// mFrameInfoMap is LIFO so keep removing what it points
// to until we hit mLastFrame.
while (mFrameInfoMap && mFrameInfoMap->mFrame != mSavedStates->mLastFrame) {
RemoveRegion(mFrameInfoMap->mFrame);
}
// If we trip this assertion it means that someone added
// PushState()/PopState() calls around code that actually
// removed mLastFrame from mFrameInfoMap, which means our
// state is now out of sync with what we thought it should be.
NS_ASSERTION(((mSavedStates->mLastFrame && mFrameInfoMap) ||
(!mSavedStates->mLastFrame && !mFrameInfoMap)),
"Unexpected outcome!");
mX = mSavedStates->mX;
mY = mSavedStates->mY;
// Now that we've restored our state, pop the topmost
// state and delete it.
SpaceManagerState *state = mSavedStates;
mSavedStates = mSavedStates->mNext;
delete state;
}
#ifdef DEBUG
void
DebugListSpaceManager(nsSpaceManager *aSpaceManager)
{
aSpaceManager->List(stdout);
}
nsresult
nsSpaceManager::List(FILE* out)
{
nsAutoString tmp;
fprintf(out, "SpaceManager@%p", this);
if (mFrame) {
nsIFrameDebug* frameDebug;
if (NS_SUCCEEDED(mFrame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**)&frameDebug))) {
frameDebug->GetFrameName(tmp);
fprintf(out, " frame=");
fputs(NS_LossyConvertUCS2toASCII(tmp).get(), out);
fprintf(out, "@%p", mFrame);
}
}
fprintf(out, " xy=%d,%d <\n", mX, mY);
if (mBandList.IsEmpty()) {
fprintf(out, " no bands\n");
}
else {
BandRect* band = mBandList.Head();
do {
fprintf(out, " left=%d top=%d right=%d bottom=%d numFrames=%d",
band->mLeft, band->mTop, band->mRight, band->mBottom,
band->mNumFrames);
if (1 == band->mNumFrames) {
nsIFrameDebug* frameDebug;
if (NS_SUCCEEDED(band->mFrame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**)&frameDebug))) {
frameDebug->GetFrameName(tmp);
fprintf(out, " frame=");
fputs(NS_LossyConvertUCS2toASCII(tmp).get(), out);
fprintf(out, "@%p", band->mFrame);
}
}
else if (1 < band->mNumFrames) {
fprintf(out, "\n ");
nsVoidArray* a = band->mFrames;
PRInt32 i, n = a->Count();
for (i = 0; i < n; i++) {
nsIFrame* frame = (nsIFrame*) a->ElementAt(i);
if (frame) {
nsIFrameDebug* frameDebug;
if (NS_SUCCEEDED(frame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**)&frameDebug))) {
frameDebug->GetFrameName(tmp);
fputs(NS_LossyConvertUCS2toASCII(tmp).get(), out);
fprintf(out, "@%p ", frame);
}
}
}
}
fprintf(out, "\n");
band = band->Next();
} while (band != mBandList.Head());
}
fprintf(out, ">\n");
return NS_OK;
}
#endif
nsSpaceManager::FrameInfo*
nsSpaceManager::GetFrameInfoFor(nsIFrame* aFrame)
{
FrameInfo* result = nsnull;
for (result = mFrameInfoMap; result; result = result->mNext) {
if (result->mFrame == aFrame) {
break;
}
}
return result;
}
nsSpaceManager::FrameInfo*
nsSpaceManager::CreateFrameInfo(nsIFrame* aFrame, const nsRect& aRect)
{
FrameInfo* frameInfo = new FrameInfo(aFrame, aRect);
if (frameInfo) {
// Link it into the list
frameInfo->mNext = mFrameInfoMap;
mFrameInfoMap = frameInfo;
}
return frameInfo;
}
void
nsSpaceManager::DestroyFrameInfo(FrameInfo* aFrameInfo)
{
// See if it's at the head of the list
if (mFrameInfoMap == aFrameInfo) {
mFrameInfoMap = aFrameInfo->mNext;
} else {
FrameInfo* prev;
// Find the previous node in the list
for (prev = mFrameInfoMap; prev && (prev->mNext != aFrameInfo); prev = prev->mNext) {
;
}
// Disconnect it from the list
NS_ASSERTION(prev, "element not in list");
if (prev) {
prev->mNext = aFrameInfo->mNext;
}
}
delete aFrameInfo;
}
/////////////////////////////////////////////////////////////////////////////
// FrameInfo
MOZ_DECL_CTOR_COUNTER(nsSpaceManager::FrameInfo)
nsSpaceManager::FrameInfo::FrameInfo(nsIFrame* aFrame, const nsRect& aRect)
: mFrame(aFrame), mRect(aRect), mNext(0)
{
MOZ_COUNT_CTOR(nsSpaceManager::FrameInfo);
}
#ifdef NS_BUILD_REFCNT_LOGGING
nsSpaceManager::FrameInfo::~FrameInfo()
{
MOZ_COUNT_DTOR(nsSpaceManager::FrameInfo);
}
#endif
/////////////////////////////////////////////////////////////////////////////
// BandRect
MOZ_DECL_CTOR_COUNTER(BandRect)
nsSpaceManager::BandRect::BandRect(nscoord aLeft,
nscoord aTop,
nscoord aRight,
nscoord aBottom,
nsIFrame* aFrame)
{
MOZ_COUNT_CTOR(BandRect);
mLeft = aLeft;
mTop = aTop;
mRight = aRight;
mBottom = aBottom;
mFrame = aFrame;
mNumFrames = 1;
}
nsSpaceManager::BandRect::BandRect(nscoord aLeft,
nscoord aTop,
nscoord aRight,
nscoord aBottom,
nsVoidArray* aFrames)
{
MOZ_COUNT_CTOR(BandRect);
mLeft = aLeft;
mTop = aTop;
mRight = aRight;
mBottom = aBottom;
mFrames = new nsVoidArray;
mFrames->operator=(*aFrames);
mNumFrames = mFrames->Count();
}
nsSpaceManager::BandRect::~BandRect()
{
MOZ_COUNT_DTOR(BandRect);
if (mNumFrames > 1) {
delete mFrames;
}
}
nsSpaceManager::BandRect*
nsSpaceManager::BandRect::SplitVertically(nscoord aBottom)
{
NS_PRECONDITION((aBottom > mTop) && (aBottom < mBottom), "bad argument");
// Create a new band rect for the bottom part
BandRect* bottomBandRect;
if (mNumFrames > 1) {
bottomBandRect = new BandRect(mLeft, aBottom, mRight, mBottom, mFrames);
} else {
bottomBandRect = new BandRect(mLeft, aBottom, mRight, mBottom, mFrame);
}
// This band rect becomes the top part, so adjust the bottom edge
mBottom = aBottom;
return bottomBandRect;
}
nsSpaceManager::BandRect*
nsSpaceManager::BandRect::SplitHorizontally(nscoord aRight)
{
NS_PRECONDITION((aRight > mLeft) && (aRight < mRight), "bad argument");
// Create a new band rect for the right part
BandRect* rightBandRect;
if (mNumFrames > 1) {
rightBandRect = new BandRect(aRight, mTop, mRight, mBottom, mFrames);
} else {
rightBandRect = new BandRect(aRight, mTop, mRight, mBottom, mFrame);
}
// This band rect becomes the left part, so adjust the right edge
mRight = aRight;
return rightBandRect;
}
PRBool
nsSpaceManager::BandRect::IsOccupiedBy(const nsIFrame* aFrame) const
{
PRBool result;
if (1 == mNumFrames) {
result = (mFrame == aFrame);
} else {
PRInt32 count = mFrames->Count();
result = PR_FALSE;
for (PRInt32 i = 0; i < count; i++) {
nsIFrame* f = (nsIFrame*)mFrames->ElementAt(i);
if (f == aFrame) {
result = PR_TRUE;
break;
}
}
}
return result;
}
void
nsSpaceManager::BandRect::AddFrame(const nsIFrame* aFrame)
{
if (1 == mNumFrames) {
nsIFrame* f = mFrame;
mFrames = new nsVoidArray;
mFrames->AppendElement(f);
}
mNumFrames++;
mFrames->AppendElement((void*)aFrame);
NS_POSTCONDITION(mFrames->Count() == mNumFrames, "bad frame count");
}
void
nsSpaceManager::BandRect::RemoveFrame(const nsIFrame* aFrame)
{
NS_PRECONDITION(mNumFrames > 1, "only one frame");
mFrames->RemoveElement((void*)aFrame);
mNumFrames--;
if (1 == mNumFrames) {
nsIFrame* f = (nsIFrame*)mFrames->ElementAt(0);
delete mFrames;
mFrame = f;
}
}
PRBool
nsSpaceManager::BandRect::HasSameFrameList(const BandRect* aBandRect) const
{
PRBool result;
// Check whether they're occupied by the same number of frames
if (mNumFrames != aBandRect->mNumFrames) {
result = PR_FALSE;
} else if (1 == mNumFrames) {
result = (mFrame == aBandRect->mFrame);
} else {
result = PR_TRUE;
// For each frame occupying this band rect check whether it also occupies
// aBandRect
PRInt32 count = mFrames->Count();
for (PRInt32 i = 0; i < count; i++) {
nsIFrame* f = (nsIFrame*)mFrames->ElementAt(i);
if (-1 == aBandRect->mFrames->IndexOf(f)) {
result = PR_FALSE;
break;
}
}
}
return result;
}
/**
* Internal helper function that counts the number of rects in this band
* including the current band rect
*/
PRInt32
nsSpaceManager::BandRect::Length() const
{
PRInt32 len = 1;
BandRect* bandRect = Next();
// Because there's a header cell we know we'll either find the next band
// (which has a different y-offset) or the header cell which has an invalid
// y-offset
while (bandRect->mTop == mTop) {
len++;
bandRect = bandRect->Next();
}
return len;
}
#ifdef DEBUG
void
nsSpaceManager::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const
{
NS_PRECONDITION(aResult, "null OUT parameter pointer");
*aResult = sizeof(*this);
// Add in the size of the band data. Don't count the header which has
// already been taken into account
if (!mBandList.IsEmpty()) {
const BandRect* bandRect = mBandList.Head();
do {
*aResult += sizeof(*bandRect);
if (bandRect->mNumFrames > 1) {
PRUint32 voidArraySize;
bandRect->mFrames->SizeOf(aHandler, &voidArraySize);
*aResult += voidArraySize;
}
bandRect = bandRect->Next();
} while (bandRect != &mBandList);
}
// Add in the size of the frame info map
for (FrameInfo* info = mFrameInfoMap; info; info = info->mNext) {
*aResult += sizeof(*info);
}
}
#endif