gecko-dev/layout/tables/nsTableRowGroupFrame.cpp
buster%netscape.com 79c1b74a7a synch with branch
fixed the following bugs: 312653 312656 312655
the fixes were:

1. cells now inherit their bgcolor from the row, if available.  This is the
Nav4 way of drawing row bgcolor, rather than having the row paint its own bgcolor. (Troy, I intend to make this conditional
based on the compatibility mode)

2. colspans across cols that are all specified width no longer try to proportionately
divide the width of the span between the cols.  see http://www.city.net (now it's really fixed, without breaking nested tables in
constrained situations.)  A happy side effect is nested tables in general behave better when constrained.

3. min table sizes are fixed, so min width changes to content now effect the
table correctly during incremental reflow.  This fixes the table layout portion of the bugs on the http://www.aol.com/corp tree.
Rick will check in the other half of this fix soon.  Until then, don't expect to see much improvement.

4. fixed bug 312799.  Table cell now always reserve at least the maxElementSize
of its content, fixing problems when desiredSize<maxElementSize
1998-08-07 02:11:51 +00:00

1256 lines
42 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "nsTableRowGroupFrame.h"
#include "nsTableRowFrame.h"
#include "nsTableCellFrame.h"
#include "nsIRenderingContext.h"
#include "nsIPresContext.h"
#include "nsIStyleContext.h"
#include "nsStyleConsts.h"
#include "nsIContent.h"
#include "nsIContentDelegate.h"
#include "nsIView.h"
#include "nsIPtr.h"
#include "nsIReflowCommand.h"
#ifdef NS_DEBUG
static PRBool gsDebug = PR_FALSE;
//#define NOISY
//#define NOISY_FLOW
#else
static const PRBool gsDebug = PR_FALSE;
#endif
NS_DEF_PTR(nsIStyleContext);
NS_DEF_PTR(nsIContent);
/* ----------- RowGroupReflowState ---------- */
struct RowGroupReflowState {
// Our reflow state
const nsReflowState& reflowState;
// The body's available size (computed from the body's parent)
nsSize availSize;
// Margin tracking information
nscoord prevMaxPosBottomMargin;
nscoord prevMaxNegBottomMargin;
// Flags for whether the max size is unconstrained
PRBool unconstrainedWidth;
PRBool unconstrainedHeight;
// Running y-offset
nscoord y;
// Flag used to set aMaxElementSize to my first row
PRBool firstRow;
// Remember the height of the first row, because it's our maxElementHeight (plus header/footers)
nscoord firstRowHeight;
RowGroupReflowState(nsIPresContext* aPresContext,
const nsReflowState& aReflowState)
: reflowState(aReflowState)
{
availSize.width = reflowState.maxSize.width;
availSize.height = reflowState.maxSize.height;
prevMaxPosBottomMargin = 0;
prevMaxNegBottomMargin = 0;
y=0; // border/padding/margin???
unconstrainedWidth = PRBool(reflowState.maxSize.width == NS_UNCONSTRAINEDSIZE);
unconstrainedHeight = PRBool(reflowState.maxSize.height == NS_UNCONSTRAINEDSIZE);
firstRow = PR_TRUE;
firstRowHeight=0;
}
~RowGroupReflowState() {
}
};
/* ----------- nsTableRowGroupFrame ---------- */
nsTableRowGroupFrame::nsTableRowGroupFrame(nsIContent* aContent, nsIFrame* aParentFrame)
: nsContainerFrame(aContent, aParentFrame)
{
mType = aContent->GetTag(); // mType: REFCNT++
}
nsTableRowGroupFrame::~nsTableRowGroupFrame()
{
if (nsnull!=mType)
NS_RELEASE(mType); // mType: REFCNT--
}
NS_METHOD nsTableRowGroupFrame::GetRowGroupType(nsIAtom *& aType)
{
NS_ADDREF(mType);
aType=mType;
return NS_OK;
}
NS_METHOD nsTableRowGroupFrame::Paint(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect)
{
// for debug...
/*
if (nsIFrame::GetShowFrameBorders()) {
aRenderingContext.SetColor(NS_RGB(128,0,0));
aRenderingContext.DrawRect(0, 0, mRect.width, mRect.height);
}
*/
PaintChildren(aPresContext, aRenderingContext, aDirtyRect);
return NS_OK;
}
// aDirtyRect is in our coordinate system
// child rect's are also in our coordinate system
/** overloaded method from nsContainerFrame. The difference is that
* we don't want to clip our children, so a cell can do a rowspan
*/
void nsTableRowGroupFrame::PaintChildren(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect)
{
nsIFrame* kid = mFirstChild;
while (nsnull != kid) {
nsIView *pView;
kid->GetView(pView);
if (nsnull == pView) {
nsRect kidRect;
kid->GetRect(kidRect);
nsRect damageArea(aDirtyRect);
// Translate damage area into kid's coordinate system
nsRect kidDamageArea(damageArea.x - kidRect.x, damageArea.y - kidRect.y,
damageArea.width, damageArea.height);
aRenderingContext.PushState();
aRenderingContext.Translate(kidRect.x, kidRect.y);
kid->Paint(aPresContext, aRenderingContext, kidDamageArea);
if (nsIFrame::GetShowFrameBorders()) {
aRenderingContext.SetColor(NS_RGB(255,0,0));
aRenderingContext.DrawRect(0, 0, kidRect.width, kidRect.height);
}
aRenderingContext.PopState();
}
else
NS_RELEASE(pView);
kid->GetNextSibling(kid);
}
}
// Collapse child's top margin with previous bottom margin
nscoord nsTableRowGroupFrame::GetTopMarginFor(nsIPresContext* aCX,
RowGroupReflowState& aState,
const nsMargin& aKidMargin)
{
nscoord margin;
nscoord maxNegTopMargin = 0;
nscoord maxPosTopMargin = 0;
if ((margin = aKidMargin.top) < 0) {
maxNegTopMargin = -margin;
} else {
maxPosTopMargin = margin;
}
nscoord maxPos = PR_MAX(aState.prevMaxPosBottomMargin, maxPosTopMargin);
nscoord maxNeg = PR_MAX(aState.prevMaxNegBottomMargin, maxNegTopMargin);
margin = maxPos - maxNeg;
return margin;
}
// Position and size aKidFrame and update our reflow state. The origin of
// aKidRect is relative to the upper-left origin of our frame, and includes
// any left/top margin.
void nsTableRowGroupFrame::PlaceChild( nsIPresContext* aPresContext,
RowGroupReflowState& aState,
nsIFrame* aKidFrame,
const nsRect& aKidRect,
nsSize* aMaxElementSize,
nsSize& aKidMaxElementSize)
{
if (PR_TRUE==gsDebug)
printf ("rowgroup %p: placing row at %d, %d, %d, %d\n",
this, aKidRect.x, aKidRect.y, aKidRect.width, aKidRect.height);
// Place and size the child
aKidFrame->SetRect(aKidRect);
// Adjust the running y-offset
aState.y += aKidRect.height;
// If our height is constrained then update the available height
if (PR_FALSE == aState.unconstrainedHeight) {
aState.availSize.height -= aKidRect.height;
}
// Update the maximum element size
if (PR_TRUE==aState.firstRow)
{
aState.firstRow = PR_FALSE;
aState.firstRowHeight = aKidRect.height;
if (nsnull != aMaxElementSize) {
aMaxElementSize->width = aKidMaxElementSize.width;
aMaxElementSize->height = aKidMaxElementSize.height;
}
}
else if (nsnull != aMaxElementSize) {
aMaxElementSize->width = PR_MAX(aMaxElementSize->width, aKidMaxElementSize.width);
}
if (gsDebug && nsnull != aMaxElementSize)
printf ("rowgroup %p: placing row %p with width = %d and MES= %d\n",
this, aKidFrame, aKidRect.width, aMaxElementSize->width);
}
/**
* Reflow the frames we've already created
*
* @param aPresContext presentation context to use
* @param aState current inline state
* @return true if we successfully reflowed all the mapped children and false
* otherwise, e.g. we pushed children to the next in flow
*/
PRBool nsTableRowGroupFrame::ReflowMappedChildren( nsIPresContext* aPresContext,
RowGroupReflowState& aState,
nsSize* aMaxElementSize)
{
#ifdef NS_DEBUG
VerifyLastIsComplete();
#endif
#ifdef NOISY
ListTag(stdout);
printf(": reflow mapped (childCount=%d) [%d,%d,%c]\n",
mChildCount,
mFirstContentOffset, mLastContentOffset,
(mLastContentIsComplete ? 'T' : 'F'));
#ifdef NOISY_FLOW
{
nsTableRowGroupFrame* flow = (nsTableRowGroupFrame*) mNextInFlow;
while (flow != 0) {
printf(" %p: [%d,%d,%c]\n",
flow, flow->mFirstContentOffset, flow->mLastContentOffset,
(flow->mLastContentIsComplete ? 'T' : 'F'));
flow = (nsTableRowGroupFrame*) flow->mNextInFlow;
}
}
#endif
#endif
NS_PRECONDITION(nsnull != mFirstChild, "no children");
if (gsDebug) printf("\n\nREFLOWMAPPED FOR ROW GROUP FRAME\n");
PRInt32 childCount = 0;
nsIFrame* prevKidFrame = nsnull;
// Remember our original mLastContentIsComplete so that if we end up
// having to push children, we have the correct value to hand to
// PushChildren.
PRBool lastContentIsComplete = mLastContentIsComplete;
nsSize kidMaxElementSize;
nsSize* pKidMaxElementSize = (nsnull != aMaxElementSize) ? &kidMaxElementSize : nsnull;
PRBool result = PR_TRUE;
for (nsIFrame* kidFrame = mFirstChild; nsnull != kidFrame; ) {
nsSize kidAvailSize(aState.availSize);
if (0>=kidAvailSize.height)
kidAvailSize.height = 1; // XXX: HaCk - we don't handle negative heights yet
nsReflowMetrics desiredSize(pKidMaxElementSize);
desiredSize.width=desiredSize.height=desiredSize.ascent=desiredSize.descent=0;
nsReflowStatus status;
// Get top margin for this kid
nsIContentPtr kid;
kidFrame->GetContent(kid.AssignRef());
nsIStyleContextPtr kidSC;
kidFrame->GetStyleContext(aPresContext, kidSC.AssignRef());
const nsStyleSpacing* kidSpacing = (const nsStyleSpacing*)
kidSC->GetStyleData(eStyleStruct_Spacing);
nsMargin kidMargin;
kidSpacing->CalcMarginFor(this, kidMargin);
nscoord topMargin = GetTopMarginFor(aPresContext, aState, kidMargin);
nscoord bottomMargin = kidMargin.bottom;
// Figure out the amount of available size for the child (subtract
// off the top margin we are going to apply to it)
if (PR_FALSE == aState.unconstrainedHeight) {
kidAvailSize.height -= topMargin;
}
// Subtract off for left and right margin
if (PR_FALSE == aState.unconstrainedWidth) {
kidAvailSize.width -= kidMargin.left + kidMargin.right;
}
// Reflow the child into the available space
nsReflowState kidReflowState(kidFrame, aState.reflowState, kidAvailSize,
eReflowReason_Resize);
kidFrame->WillReflow(*aPresContext);
kidFrame->MoveTo(kidMargin.left, aState.y + topMargin);
status = ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowState);
// Did the child fit?
if ((kidFrame != mFirstChild) &&
((kidAvailSize.height <= 0) ||
(desiredSize.height > kidAvailSize.height)))
{
// The child's height is too big to fit at all in our remaining space,
// and it's not our first child.
//
// Note that if the width is too big that's okay and we allow the
// child to extend horizontally outside of the reflow area
// Since we are giving the next-in-flow our last child, we
// give it our original mLastContentIsComplete, too (in case we
// are pushing into an empty next-in-flow)
PushChildren(kidFrame, prevKidFrame, lastContentIsComplete);
PRInt32 contentIndex;
prevKidFrame->GetContentIndex(contentIndex);
SetLastContentOffset(contentIndex);
// Our mLastContentIsComplete was already set by the last kid we
// reflowed reflow's status
result = PR_FALSE;
break;
}
// Place the child after taking into account it's margin
nsRect kidRect (kidMargin.left, aState.y, desiredSize.width, desiredSize.height);
PlaceChild(aPresContext, aState, kidFrame, kidRect, aMaxElementSize,
kidMaxElementSize);
if (bottomMargin < 0) {
aState.prevMaxNegBottomMargin = -bottomMargin;
} else {
aState.prevMaxPosBottomMargin = bottomMargin;
}
childCount++;
// Remember where we just were in case we end up pushing children
prevKidFrame = kidFrame;
// Update mLastContentIsComplete now that this kid fits
mLastContentIsComplete = NS_FRAME_IS_COMPLETE(status);
/* Row groups should not create continuing frames for rows
* unless they absolutely have to!
* check to see if this is absolutely necessary (with new params from troy)
* otherwise PushChildren and bail.
*/
// Special handling for incomplete children
if (NS_FRAME_IS_NOT_COMPLETE(status)) {
// XXX It's good to assume that we might still have room
// even if the child didn't complete (floaters will want this)
nsIFrame* kidNextInFlow;
kidFrame->GetNextInFlow(kidNextInFlow);
PRBool lastContentIsComplete = mLastContentIsComplete;
if (nsnull == kidNextInFlow) {
// No the child isn't complete, and it doesn't have a next in flow so
// create a continuing frame. This hooks the child into the flow.
nsIFrame* continuingFrame;
nsIStyleContext* kidSC;
kidFrame->GetStyleContext(aPresContext, kidSC);
kidFrame->CreateContinuingFrame(*aPresContext, this, kidSC,
continuingFrame);
NS_RELEASE(kidSC);
// Insert the frame. We'll reflow it next pass through the loop
nsIFrame* nextSib;
kidFrame->GetNextSibling(nextSib);
continuingFrame->SetNextSibling(nextSib);
kidFrame->SetNextSibling(continuingFrame);
if (nsnull == nextSib) {
// Assume that the continuation frame we just created is
// complete, for now. It will get reflowed by our
// next-in-flow (we are going to push it now)
lastContentIsComplete = PR_TRUE;
}
}
// We've used up all of our available space so push the remaining
// children to the next-in-flow
nsIFrame* nextSibling;
kidFrame->GetNextSibling(nextSibling);
if (nsnull != nextSibling) {
PushChildren(nextSibling, kidFrame, lastContentIsComplete);
PRInt32 contentIndex;
prevKidFrame->GetContentIndex(contentIndex);
SetLastContentOffset(contentIndex);
}
result = PR_FALSE;
break;
}
// Add back in the left and right margins, because one row does not
// impact another row's width
if (PR_FALSE == aState.unconstrainedWidth) {
kidAvailSize.width += kidMargin.left + kidMargin.right;
}
// Get the next child
kidFrame->GetNextSibling(kidFrame);
}
// Update the child count
mChildCount = childCount;
#ifdef NS_DEBUG
NS_POSTCONDITION(LengthOf(mFirstChild) == mChildCount, "bad child count");
nsIFrame* lastChild;
PRInt32 lastIndexInParent;
LastChild(lastChild);
lastChild->GetContentIndex(lastIndexInParent);
NS_POSTCONDITION(lastIndexInParent == mLastContentOffset, "bad last content offset");
#endif
#ifdef NS_DEBUG
VerifyLastIsComplete();
#endif
#ifdef NOISY
ListTag(stdout);
printf(": reflow mapped %sok (childCount=%d) [%d,%d,%c]\n",
(result ? "" : "NOT "),
mChildCount,
mFirstContentOffset, mLastContentOffset,
(mLastContentIsComplete ? 'T' : 'F'));
#ifdef NOISY_FLOW
{
nsTableRowGroupFrame* flow = (nsTableRowGroupFrame*) mNextInFlow;
while (flow != 0) {
printf(" %p: [%d,%d,%c]\n",
flow, flow->mFirstContentOffset, flow->mLastContentOffset,
(flow->mLastContentIsComplete ? 'T' : 'F'));
flow = (nsTableRowGroupFrame*) flow->mNextInFlow;
}
}
#endif
#endif
return result;
}
/**
* Try and pull-up frames from our next-in-flow
*
* @param aPresContext presentation context to use
* @param aState current inline state
* @return true if we successfully pulled-up all the children and false
* otherwise, e.g. child didn't fit
*/
PRBool nsTableRowGroupFrame::PullUpChildren(nsIPresContext* aPresContext,
RowGroupReflowState& aState,
nsSize* aMaxElementSize)
{
#ifdef NS_DEBUG
VerifyLastIsComplete();
#endif
#ifdef NOISY
ListTag(stdout);
printf(": pullup (childCount=%d) [%d,%d,%c]\n",
mChildCount,
mFirstContentOffset, mLastContentOffset,
(mLastContentIsComplete ? 'T' : 'F'));
#ifdef NOISY_FLOW
{
nsTableRowGroupFrame* flow = (nsTableRowGroupFrame*) mNextInFlow;
while (flow != 0) {
printf(" %p: [%d,%d,%c]\n",
flow, flow->mFirstContentOffset, flow->mLastContentOffset,
(flow->mLastContentIsComplete ? 'T' : 'F'));
flow = (nsTableRowGroupFrame*) flow->mNextInFlow;
}
}
#endif
#endif
nsTableRowGroupFrame* nextInFlow = (nsTableRowGroupFrame*)mNextInFlow;
nsSize kidMaxElementSize;
nsSize* pKidMaxElementSize = (nsnull != aMaxElementSize) ? &kidMaxElementSize : nsnull;
#ifdef NS_DEBUG
PRInt32 kidIndex = NextChildOffset();
#endif
nsIFrame* prevKidFrame;
LastChild(prevKidFrame);
// This will hold the prevKidFrame's mLastContentIsComplete
// status. If we have to push the frame that follows prevKidFrame
// then this will become our mLastContentIsComplete state. Since
// prevKidFrame is initially our last frame, it's completion status
// is our mLastContentIsComplete value.
PRBool prevLastContentIsComplete = mLastContentIsComplete;
PRBool result = PR_TRUE;
while (nsnull != nextInFlow) {
nsReflowMetrics kidSize(pKidMaxElementSize);
kidSize.width=kidSize.height=kidSize.ascent=kidSize.descent=0;
nsReflowStatus status;
// Get the next child
nsIFrame* kidFrame = nextInFlow->mFirstChild;
// Any more child frames?
if (nsnull == kidFrame) {
// No. Any frames on its overflow list?
if (nsnull != nextInFlow->mOverflowList) {
// Move the overflow list to become the child list
// NS_ABORT();
nextInFlow->AppendChildren(nextInFlow->mOverflowList);
nextInFlow->mOverflowList = nsnull;
kidFrame = nextInFlow->mFirstChild;
} else {
// We've pulled up all the children, so move to the next-in-flow.
nextInFlow->GetNextInFlow((nsIFrame*&)nextInFlow);
continue;
}
}
// See if the child fits in the available space. If it fits or
// it's splittable then reflow it. The reason we can't just move
// it is that we still need ascent/descent information
nsSize kidFrameSize;
nsSplittableType kidIsSplittable;
kidFrame->GetSize(kidFrameSize);
kidFrame->IsSplittable(kidIsSplittable);
if ((kidFrameSize.height > aState.availSize.height) &&
NS_FRAME_IS_NOT_SPLITTABLE(kidIsSplittable)) {
result = PR_FALSE;
mLastContentIsComplete = prevLastContentIsComplete;
break;
}
nsReflowState kidReflowState(kidFrame, aState.reflowState, aState.availSize,
eReflowReason_Resize);
kidFrame->WillReflow(*aPresContext);
status = ReflowChild(kidFrame, aPresContext, kidSize, kidReflowState);
// Did the child fit?
if ((kidSize.height > aState.availSize.height) && (nsnull != mFirstChild)) {
// The child is too wide to fit in the available space, and it's
// not our first child
result = PR_FALSE;
mLastContentIsComplete = prevLastContentIsComplete;
break;
}
// Place the child
//aState.y += topMargin;
nsRect kidRect (0, 0, kidSize.width, kidSize.height);
//kidRect.x += kidMol->margin.left;
kidRect.y += aState.y;
PlaceChild(aPresContext, aState, kidFrame, kidRect, aMaxElementSize, *pKidMaxElementSize);
// Remove the frame from its current parent
kidFrame->GetNextSibling(nextInFlow->mFirstChild);
nextInFlow->mChildCount--;
// Update the next-in-flows first content offset
if (nsnull != nextInFlow->mFirstChild) {
PRInt32 contentIndex;
nextInFlow->mFirstChild->GetContentIndex(contentIndex);
nextInFlow->SetFirstContentOffset(contentIndex);
}
// Link the frame into our list of children
kidFrame->SetGeometricParent(this);
nsIFrame* kidContentParent;
kidFrame->GetContentParent(kidContentParent);
if (nextInFlow == kidContentParent) {
kidFrame->SetContentParent(this);
}
if (nsnull == prevKidFrame) {
mFirstChild = kidFrame;
PRInt32 contentIndex;
kidFrame->GetContentIndex(contentIndex);
SetFirstContentOffset(contentIndex);
} else {
prevKidFrame->SetNextSibling(kidFrame);
}
kidFrame->SetNextSibling(nsnull);
mChildCount++;
// Remember where we just were in case we end up pushing children
prevKidFrame = kidFrame;
prevLastContentIsComplete = mLastContentIsComplete;
// Is the child we just pulled up complete?
mLastContentIsComplete = NS_FRAME_IS_COMPLETE(status);
if (NS_FRAME_IS_NOT_COMPLETE(status)) {
// No the child isn't complete
nsIFrame* kidNextInFlow;
kidFrame->GetNextInFlow(kidNextInFlow);
if (nsnull == kidNextInFlow) {
// The child doesn't have a next-in-flow so create a
// continuing frame. The creation appends it to the flow and
// prepares it for reflow.
nsIFrame* continuingFrame;
nsIStyleContext* kidSC;
kidFrame->GetStyleContext(aPresContext, kidSC);
kidFrame->CreateContinuingFrame(*aPresContext, this, kidSC,
continuingFrame);
NS_RELEASE(kidSC);
NS_ASSERTION(nsnull != continuingFrame, "frame creation failed");
// Add the continuing frame to our sibling list and then push
// it to the next-in-flow. This ensures the next-in-flow's
// content offsets and child count are set properly. Note that
// we can safely assume that the continuation is complete so
// we pass PR_TRUE into PushChidren in case our next-in-flow
// was just drained and now needs to know it's
// mLastContentIsComplete state.
kidFrame->SetNextSibling(continuingFrame);
PushChildren(continuingFrame, kidFrame, PR_TRUE);
// After we push the continuation frame we don't need to fuss
// with mLastContentIsComplete beause the continuation frame
// is no longer on *our* list.
}
// If the child isn't complete then it means that we've used up
// all of our available space.
result = PR_FALSE;
break;
}
}
// Update our last content offset
if (nsnull != prevKidFrame) {
NS_ASSERTION(IsLastChild(prevKidFrame), "bad last child");
PRInt32 contentIndex;
prevKidFrame->GetContentIndex(contentIndex);
SetLastContentOffset(contentIndex);
}
// We need to make sure the first content offset is correct for any empty
// next-in-flow frames (frames where we pulled up all the child frames)
nextInFlow = (nsTableRowGroupFrame*)mNextInFlow;
if ((nsnull != nextInFlow) && (nsnull == nextInFlow->mFirstChild)) {
// We have at least one empty frame. Did we succesfully pull up all the
// child frames?
if (PR_FALSE == result) {
// No, so we need to adjust the first content offset of all the empty
// frames
AdjustOffsetOfEmptyNextInFlows();
#ifdef NS_DEBUG
} else {
// Yes, we successfully pulled up all the child frames which means all
// the next-in-flows must be empty. Do a sanity check
while (nsnull != nextInFlow) {
NS_ASSERTION(nsnull == nextInFlow->mFirstChild, "non-empty next-in-flow");
nextInFlow->GetNextInFlow((nsIFrame*&)nextInFlow);
}
#endif
}
}
#ifdef NS_DEBUG
VerifyLastIsComplete();
#endif
#ifdef NOISY
ListTag(stdout);
printf(": pullup %sok (childCount=%d) [%d,%d,%c]\n",
(result ? "" : "NOT "),
mChildCount,
mFirstContentOffset, mLastContentOffset,
(mLastContentIsComplete ? 'T' : 'F'));
#ifdef NOISY_FLOW
{
nsTableRowGroupFrame* flow = (nsTableRowGroupFrame*) mNextInFlow;
while (flow != 0) {
printf(" %p: [%d,%d,%c]\n",
flow, flow->mFirstContentOffset, flow->mLastContentOffset,
(flow->mLastContentIsComplete ? 'T' : 'F'));
flow = (nsTableRowGroupFrame*) flow->mNextInFlow;
}
}
#endif
#endif
return result;
}
/**
* Create new frames for content we haven't yet mapped
*
* @param aPresContext presentation context to use
* @param aState current inline state
* @return frComplete if all content has been mapped and frNotComplete
* if we should be continued
*/
nsReflowStatus
nsTableRowGroupFrame::ReflowUnmappedChildren(nsIPresContext* aPresContext,
RowGroupReflowState& aState,
nsSize* aMaxElementSize)
{
#ifdef NS_DEBUG
VerifyLastIsComplete();
#endif
nsIFrame* kidPrevInFlow = nsnull;
nsReflowStatus result = NS_FRAME_NOT_COMPLETE;
// If we have no children and we have a prev-in-flow then we need to pick
// up where it left off. If we have children, e.g. we're being resized, then
// our content offset should already be set correctly...
if ((nsnull == mFirstChild) && (nsnull != mPrevInFlow)) {
nsTableRowGroupFrame* prev = (nsTableRowGroupFrame*)mPrevInFlow;
NS_ASSERTION(prev->mLastContentOffset >= prev->mFirstContentOffset, "bad prevInFlow");
mFirstContentOffset = prev->NextChildOffset();
if (!prev->mLastContentIsComplete) {
// Our prev-in-flow's last child is not complete
prev->LastChild(kidPrevInFlow);
}
}
mLastContentIsComplete = PR_TRUE;
// Place our children, one at a time, until we are out of children
nsSize kidMaxElementSize;
nsSize* pKidMaxElementSize = (nsnull != aMaxElementSize) ? &kidMaxElementSize : nsnull;
PRInt32 kidIndex = NextChildOffset();
nsIFrame* prevKidFrame;
LastChild(prevKidFrame); // XXX remember this...
for (;;) {
// Get the next content object
nsIContentPtr kid = mContent->ChildAt(kidIndex);
if (kid.IsNull()) {
result = NS_FRAME_COMPLETE;
break;
}
// Make sure we still have room left
if (aState.availSize.height <= 0) {
// Note: return status was set to frNotComplete above...
break;
}
// Resolve style
nsIStyleContextPtr kidSC =
aPresContext->ResolveStyleContextFor(kid, this, PR_TRUE);
const nsStyleSpacing* kidSpacing = (const nsStyleSpacing*)
kidSC->GetStyleData(eStyleStruct_Spacing);
nsMargin kidMargin;
kidSpacing->CalcMarginFor(this, kidMargin);
nscoord topMargin = GetTopMarginFor(aPresContext, aState, kidMargin);
nscoord bottomMargin = kidMargin.bottom;
nsIFrame* kidFrame;
// Create a child frame
if (nsnull == kidPrevInFlow) {
nsIContentDelegate* kidDel = nsnull;
kidDel = kid->GetDelegate(aPresContext);
nsresult rv = kidDel->CreateFrame(aPresContext, kid, this, kidSC,
kidFrame);
NS_RELEASE(kidDel);
} else {
kidPrevInFlow->CreateContinuingFrame(*aPresContext, this, kidSC,
kidFrame);
}
// Link child frame into the list of children
if (nsnull != prevKidFrame) {
prevKidFrame->SetNextSibling(kidFrame);
} else {
mFirstChild = kidFrame; // our first child
SetFirstContentOffset(kidIndex);
}
mChildCount++;
// Try to reflow the child into the available space. It might not
// fit or might need continuing.
nsReflowMetrics kidSize(pKidMaxElementSize);
kidSize.width=kidSize.height=kidSize.ascent=kidSize.descent=0;
nsReflowState kidReflowState(kidFrame, aState.reflowState, aState.availSize,
eReflowReason_Initial);
kidFrame->WillReflow(*aPresContext);
kidFrame->MoveTo(0, aState.y);
nsReflowStatus status = ReflowChild(kidFrame,aPresContext, kidSize,
kidReflowState);
// Did the child fit?
if ((kidSize.height > aState.availSize.height) && (nsnull != mFirstChild)) {
// The child is too wide to fit in the available space, and it's
// not our first child. Add the frame to our overflow list
NS_ASSERTION(nsnull == mOverflowList, "bad overflow list");
mOverflowList = kidFrame;
prevKidFrame->SetNextSibling(nsnull);
break;
}
// Place the child
//aState.y += topMargin;
nsRect kidRect (0, 0, kidSize.width, kidSize.height);
//kidRect.x += kidMol->margin.left;
kidRect.y += aState.y;
PlaceChild(aPresContext, aState, kidFrame, kidRect, aMaxElementSize, *pKidMaxElementSize);
prevKidFrame = kidFrame;
kidIndex++;
// Did the child complete?
if (NS_FRAME_IS_NOT_COMPLETE(status)) {
// If the child isn't complete then it means that we've used up
// all of our available space
mLastContentIsComplete = PR_FALSE;
break;
}
kidPrevInFlow = nsnull;
}
// Update the content mapping
NS_ASSERTION(IsLastChild(prevKidFrame), "bad last child");
PRInt32 contentIndex;
prevKidFrame->GetContentIndex(contentIndex);
SetLastContentOffset(contentIndex);
#ifdef NS_DEBUG
PRInt32 len = LengthOf(mFirstChild);
NS_ASSERTION(len == mChildCount, "bad child count");
#endif
#ifdef NS_DEBUG
VerifyLastIsComplete();
#endif
return result;
}
/**
*/
void nsTableRowGroupFrame::ShrinkWrapChildren(nsIPresContext* aPresContext,
nsReflowMetrics& aDesiredSize)
{
// iterate children and for each row get its height
PRBool atLeastOneRowSpanningCell = PR_FALSE;
nscoord topInnerMargin = 0;
nscoord bottomInnerMargin = 0;
PRInt32 numRows;
ChildCount(numRows); //XXX: this doesn't work if we allow non-table row content.
// we actually need to go through our content and count the
// children that are display type == table row
PRInt32 *rowHeights = new PRInt32[numRows];
nsCRT::memset (rowHeights, 0, numRows*sizeof(PRInt32));
/* Step 1: get the height of the tallest cell in the row and save it for
* pass 2
*/
nsTableRowFrame* rowFrame = (nsTableRowFrame*)mFirstChild;
PRInt32 rowIndex = 0;
while (nsnull != rowFrame)
{
// get the height of the tallest cell in the row (excluding cells that span rows)
nscoord maxCellHeight = rowFrame->GetTallestChild();
nscoord maxCellTopMargin = rowFrame->GetChildMaxTopMargin();
nscoord maxCellBottomMargin = rowFrame->GetChildMaxBottomMargin();
nscoord maxRowHeight = maxCellHeight + maxCellTopMargin + maxCellBottomMargin;
// save the row height for pass 2 below
rowHeights[rowIndex] = maxRowHeight;
// Update top and bottom inner margin if applicable
if (0 == rowIndex) {
topInnerMargin = maxCellTopMargin;
}
if ((rowIndex + 1) == numRows) {
bottomInnerMargin = maxCellBottomMargin;
}
// Get the next row
rowFrame->GetNextSibling((nsIFrame*&)rowFrame);
rowIndex++;
}
/* Step 2: now account for cells that span rows.
* a spanning cell's height is the sum of the heights of the rows it spans,
* or it's own desired height, whichever is greater.
* If the cell's desired height is the larger value, resize the rows and contained
* cells by an equal percentage of the additional space.
*/
/* TODO
* 1. optimization, if (PR_TRUE==atLeastOneRowSpanningCell) ... otherwise skip this step entirely
* 2. find cases where spanning cells effect other spanning cells that began in rows above themselves.
* I think in this case, we have to make another pass through step 2.
* There should be a "rational" check to terminate that kind of loop after n passes, probably 3 or 4.
*/
PRInt32 rowGroupHeight = 0;
rowFrame = (nsTableRowFrame*)mFirstChild;
rowIndex = 0;
while (nsnull != rowFrame)
{
// check this row for a cell with rowspans
nsTableCellFrame* cellFrame;
rowFrame->FirstChild((nsIFrame*&)cellFrame);
while (nsnull != cellFrame)
{
PRInt32 rowSpan = ((nsTableFrame*)mGeometricParent)->GetEffectiveRowSpan(rowIndex,
cellFrame);
if (rowSpan > 1)
{ // found a cell with rowspan > 1, determine its height
nscoord heightOfRowsSpanned = 0;
PRInt32 i;
for ( i = 0; i < rowSpan; i++)
heightOfRowsSpanned += rowHeights[rowIndex + i];
heightOfRowsSpanned -= topInnerMargin + bottomInnerMargin;
/* if the cell height fits in the rows, expand the spanning cell's height and slap it in */
nsSize cellFrameSize;
cellFrame->GetSize(cellFrameSize);
if (heightOfRowsSpanned > cellFrameSize.height)
{
cellFrame->SizeTo(cellFrameSize.width, heightOfRowsSpanned);
// Realign cell content based on new height
cellFrame->VerticallyAlignChild(aPresContext);
}
/* otherwise, distribute the excess height to the rows effected.
* push all subsequent rows down by the total change in height of all the rows above it
*/
else
{
PRInt32 excessHeight = cellFrameSize.height - heightOfRowsSpanned;
PRInt32 excessHeightPerRow = excessHeight/rowSpan;
// for every row starting at the row with the spanning cell...
nsTableRowFrame *rowFrameToBeResized = rowFrame;
for (i = rowIndex; i < numRows; i++)
{
// if the row is within the spanned range, resize the row
if (i < (rowIndex + rowSpan))
{
// update the row height
rowHeights[i] += excessHeightPerRow;
// adjust the height of the row
nsSize rowFrameSize;
rowFrameToBeResized->GetSize(rowFrameSize);
rowFrameToBeResized->SizeTo(rowFrameSize.width, rowHeights[i]);
}
// if we're dealing with a row below the row containing the spanning cell,
// push that row down by the amount we've expanded the cell heights by
if ((i >= rowIndex) && (i != 0))
{
nsRect rowRect;
rowFrameToBeResized->GetRect(rowRect);
nscoord delta = excessHeightPerRow * (i - rowIndex);
if (delta > excessHeight)
delta = excessHeight;
rowFrameToBeResized->MoveTo(rowRect.x, rowRect.y + delta);
}
// Get the next row frame
rowFrameToBeResized->GetNextSibling((nsIFrame*&)rowFrameToBeResized);
}
}
}
// Get the next cell frame
cellFrame->GetNextSibling((nsIFrame*&)cellFrame);
}
// Update the running row group height
rowGroupHeight += rowHeights[rowIndex];
// Get the next row
rowFrame->GetNextSibling((nsIFrame*&)rowFrame);
rowIndex++;
}
// Adjust our desired size
aDesiredSize.height = rowGroupHeight;
// cleanup
delete []rowHeights;
}
nsresult nsTableRowGroupFrame::AdjustSiblingsAfterReflow(nsIPresContext* aPresContext,
RowGroupReflowState& aState,
nsIFrame* aKidFrame,
nscoord aDeltaY)
{
nsIFrame* lastKidFrame = aKidFrame;
if (aDeltaY != 0) {
// Move the frames that follow aKidFrame by aDeltaY
nsIFrame* kidFrame;
aKidFrame->GetNextSibling(kidFrame);
while (nsnull != kidFrame) {
nsPoint origin;
// XXX We can't just slide the child if it has a next-in-flow
kidFrame->GetOrigin(origin);
origin.y += aDeltaY;
// XXX We need to send move notifications to the frame...
kidFrame->WillReflow(*aPresContext);
kidFrame->MoveTo(origin.x, origin.y);
// Get the next frame
lastKidFrame = kidFrame;
kidFrame->GetNextSibling(kidFrame);
}
} else {
// Get the last frame
LastChild(lastKidFrame);
}
// XXX Deal with cells that have rowspans.
// Update our running y-offset to reflect the bottommost child
nsRect rect;
lastKidFrame->GetRect(rect);
aState.y = rect.YMost();
return NS_OK;
}
/** Layout the entire row group.
* This method stacks rows vertically according to HTML 4.0 rules.
* Rows are responsible for layout of their children.
*/
NS_METHOD
nsTableRowGroupFrame::Reflow(nsIPresContext& aPresContext,
nsReflowMetrics& aDesiredSize,
const nsReflowState& aReflowState,
nsReflowStatus& aStatus)
{
if (gsDebug==PR_TRUE)
printf("nsTableRowGroupFrame::Reflow - aMaxSize = %d, %d\n",
aReflowState.maxSize.width, aReflowState.maxSize.height);
#ifdef NS_DEBUG
PreReflowCheck();
#endif
// Initialize out parameter
if (nsnull != aDesiredSize.maxElementSize) {
aDesiredSize.maxElementSize->width = 0;
aDesiredSize.maxElementSize->height = 0;
}
RowGroupReflowState state(&aPresContext, aReflowState);
if (eReflowReason_Incremental == aReflowState.reason) {
nsIFrame* target;
aReflowState.reflowCommand->GetTarget(target);
if (this == target) {
NS_NOTYETIMPLEMENTED("unexpected reflow command");
}
// XXX Recover state
// XXX Deal with the case where the reflow command is targeted at us
nsIFrame* kidFrame;
aReflowState.reflowCommand->GetNext(kidFrame);
// Remember the old rect
nsRect oldKidRect;
kidFrame->GetRect(oldKidRect);
// Pass along the reflow command
// XXX Correctly compute the available space...
nsReflowState kidReflowState(kidFrame, aReflowState, aReflowState.maxSize);
nsReflowMetrics desiredSize(nsnull);
kidFrame->WillReflow(aPresContext);
aStatus = ReflowChild(kidFrame, &aPresContext, desiredSize, kidReflowState);
// Resize the row frame
nsRect kidRect;
kidFrame->GetRect(kidRect);
kidFrame->SizeTo(desiredSize.width, desiredSize.height);
// Adjust the frames that follow...
AdjustSiblingsAfterReflow(&aPresContext, state, kidFrame, desiredSize.height -
oldKidRect.height);
// Return of desired size
aDesiredSize.width = aReflowState.maxSize.width;
aDesiredSize.height = state.y;
} else {
PRBool reflowMappedOK = PR_TRUE;
aStatus = NS_FRAME_COMPLETE;
// Check for an overflow list
MoveOverflowToChildList();
// Reflow the existing frames
if (nsnull != mFirstChild) {
reflowMappedOK = ReflowMappedChildren(&aPresContext, state, aDesiredSize.maxElementSize);
if (PR_FALSE == reflowMappedOK) {
aStatus = NS_FRAME_NOT_COMPLETE;
}
}
// Did we successfully reflow our mapped children?
if (PR_TRUE == reflowMappedOK) {
// Any space left?
if (state.availSize.height <= 0) {
// No space left. Don't try to pull-up children or reflow unmapped
if (NextChildOffset() < mContent->ChildCount()) {
aStatus = NS_FRAME_NOT_COMPLETE;
}
} else if (NextChildOffset() < mContent->ChildCount()) {
// Try and pull-up some children from a next-in-flow
if (PullUpChildren(&aPresContext, state, aDesiredSize.maxElementSize)) {
// If we still have unmapped children then create some new frames
if (NextChildOffset() < mContent->ChildCount()) {
aStatus = ReflowUnmappedChildren(&aPresContext, state, aDesiredSize.maxElementSize);
}
} else {
// We were unable to pull-up all the existing frames from the
// next in flow
aStatus = NS_FRAME_NOT_COMPLETE;
}
}
}
if (NS_FRAME_IS_COMPLETE(aStatus)) {
// Don't forget to add in the bottom margin from our last child.
// Only add it in if there's room for it.
nscoord margin = state.prevMaxPosBottomMargin -
state.prevMaxNegBottomMargin;
if (state.availSize.height >= margin) {
state.y += margin;
}
}
// Return our desired rect
//NS_ASSERTION(0<state.firstRowHeight, "illegal firstRowHeight after reflow");
//NS_ASSERTION(0<state.y, "illegal height after reflow");
aDesiredSize.width = aReflowState.maxSize.width;
aDesiredSize.height = state.y;
// shrink wrap rows to height of tallest cell in that row
ShrinkWrapChildren(&aPresContext, aDesiredSize);
}
#ifdef NS_DEBUG
PostReflowCheck(aStatus);
#endif
if (gsDebug==PR_TRUE)
{
if (nsnull!=aDesiredSize.maxElementSize)
printf("nsTableRowGroupFrame %p returning: %s with aDesiredSize=%d,%d, aMES=%d,%d\n",
this, NS_FRAME_IS_COMPLETE(aStatus)?"Complete":"Not Complete",
aDesiredSize.width, aDesiredSize.height,
aDesiredSize.maxElementSize->width, aDesiredSize.maxElementSize->height);
else
printf("nsTableRowGroupFrame %p returning: %s with aDesiredSize=%d,%d, aMES=NSNULL\n",
this, NS_FRAME_IS_COMPLETE(aStatus)?"Complete":"Not Complete",
aDesiredSize.width, aDesiredSize.height);
}
return NS_OK;
}
NS_METHOD
nsTableRowGroupFrame::CreateContinuingFrame(nsIPresContext& aPresContext,
nsIFrame* aParent,
nsIStyleContext* aStyleContext,
nsIFrame*& aContinuingFrame)
{
nsTableRowGroupFrame* cf = new nsTableRowGroupFrame(mContent, aParent);
if (nsnull == cf) {
return NS_ERROR_OUT_OF_MEMORY;
}
PrepareContinuingFrame(aPresContext, aParent, aStyleContext, cf);
if (PR_TRUE==gsDebug) printf("nsTableRowGroupFrame::CCF parent = %p, this=%p, cf=%p\n", aParent, this, cf);
aContinuingFrame = cf;
return NS_OK;
}
/* ----- static methods ----- */
nsresult nsTableRowGroupFrame::NewFrame(nsIFrame** aInstancePtrResult,
nsIContent* aContent,
nsIFrame* aParent)
{
NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr");
if (nsnull == aInstancePtrResult) {
return NS_ERROR_NULL_POINTER;
}
nsIFrame* it = new nsTableRowGroupFrame(aContent, aParent);
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
*aInstancePtrResult = it;
return NS_OK;
}
// For Debugging ONLY
NS_METHOD nsTableRowGroupFrame::MoveTo(nscoord aX, nscoord aY)
{
if ((aX != mRect.x) || (aY != mRect.y)) {
mRect.x = aX;
mRect.y = aY;
nsIView* view;
GetView(view);
// Let the view know
if (nsnull != view) {
// Position view relative to it's parent, not relative to our
// parent frame (our parent frame may not have a view).
nsIView* parentWithView;
nsPoint origin;
GetOffsetFromView(origin, parentWithView);
view->SetPosition(origin.x, origin.y);
NS_IF_RELEASE(parentWithView);
}
}
return NS_OK;
}
NS_METHOD nsTableRowGroupFrame::SizeTo(nscoord aWidth, nscoord aHeight)
{
mRect.width = aWidth;
mRect.height = aHeight;
nsIView* view;
GetView(view);
// Let the view know
if (nsnull != view) {
view->SetDimensions(aWidth, aHeight);
}
return NS_OK;
}