Mozilla/mozilla/layout/html/table/src/nsTableOuterFrame.cpp
rods%netscape.com b23e7af50b This checkin enables mozilla to support the printing of selection, the printing of page ranges, and
the printing of headers and footers.
Printing of selection is implemented by the frames figuring out if they are in the selection and painting
if they or not they they don't paint. This also only allows the printing of the first page of
selections, alothough it is well documented where this is implemeted so it can be removed.
Bugs 63426, 31218, 61075 r=dcone,kmcclusk,erik,buster sr=waterson


git-svn-id: svn://10.0.0.236/trunk@85624 18797224-902f-48f8-a5cc-f745e15eee43
2001-01-27 14:09:34 +00:00

1676 lines
61 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.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 Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Pierre Phaneuf <pp@ludusdesign.com>
*/
#include "nsTableOuterFrame.h"
#include "nsTableFrame.h"
#include "nsIReflowCommand.h"
#include "nsIStyleContext.h"
#include "nsStyleConsts.h"
#include "nsIPresContext.h"
#include "nsIRenderingContext.h"
#include "nsCSSRendering.h"
#include "nsIContent.h"
#include "nsVoidArray.h"
#include "nsIPtr.h"
#include "prinrval.h"
#include "nsHTMLIIDs.h"
#include "nsLayoutAtoms.h"
#include "nsHTMLParts.h"
#include "nsIPresShell.h"
/* ----------- nsTableCaptionFrame ---------- */
#define NS_TABLE_FRAME_CAPTION_LIST_INDEX 0
#define NO_SIDE 100
// caption frame
nsTableCaptionFrame::nsTableCaptionFrame()
{
// shrink wrap
SetFlags(NS_BLOCK_SPACE_MGR|NS_BLOCK_WRAP_SIZE);
}
nsTableCaptionFrame::~nsTableCaptionFrame()
{
}
NS_IMETHODIMP
nsTableOuterFrame::Destroy(nsIPresContext* aPresContext)
{
if (mCaptionFrame) {
mCaptionFrame->Destroy(aPresContext);
}
return nsHTMLContainerFrame::Destroy(aPresContext);
}
NS_IMETHODIMP
nsTableCaptionFrame::GetFrameType(nsIAtom** aType) const
{
NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer");
*aType = nsLayoutAtoms::tableCaptionFrame;
NS_ADDREF(*aType);
return NS_OK;
}
nsresult
NS_NewTableCaptionFrame(nsIPresShell* aPresShell,
nsIFrame** aNewFrame)
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
if (nsnull == aNewFrame) {
return NS_ERROR_NULL_POINTER;
}
nsTableCaptionFrame* it = new (aPresShell) nsTableCaptionFrame;
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
*aNewFrame = it;
return NS_OK;
}
/* ----------- nsTableOuterFrame ---------- */
NS_IMPL_ADDREF_INHERITED(nsTableOuterFrame, nsHTMLContainerFrame)
NS_IMPL_RELEASE_INHERITED(nsTableOuterFrame, nsHTMLContainerFrame)
nsTableOuterFrame::nsTableOuterFrame()
{
#ifdef DEBUG_TABLE_REFLOW_TIMING
mTimer = new nsReflowTimer(this);
#endif
}
nsTableOuterFrame::~nsTableOuterFrame()
{
#ifdef DEBUG_TABLE_REFLOW_TIMING
nsTableFrame::DebugReflowDone(this);
#endif
}
nsresult nsTableOuterFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if (aIID.Equals(NS_GET_IID(nsITableLayout)))
{ // note there is no addref here, frames are not addref'd
*aInstancePtr = (void*)(nsITableLayout*)this;
return NS_OK;
} else {
return nsHTMLContainerFrame::QueryInterface(aIID, aInstancePtr);
}
}
// helper, should really be in nsFrame
void nsTableOuterFrame::PositionView(nsIPresContext* aPresContext,
nsIFrame* aFrame)
{
nsIView* view;
aFrame->GetView(aPresContext, &view);
if (view) {
nsContainerFrame::SyncFrameViewAfterReflow(aPresContext, aFrame, view, nsnull);
}
else {
nsContainerFrame::PositionChildViews(aPresContext, aFrame);
}
}
NS_IMETHODIMP
nsTableOuterFrame::IsPercentageBase(PRBool& aBase) const
{
aBase = PR_FALSE;
return NS_OK;
}
// tables change 0 width into auto, trees override this and do nothing
NS_IMETHODIMP
nsTableOuterFrame::AdjustZeroWidth()
{
// a 0 width table becomes auto
PRBool makeAuto = PR_FALSE;
nsStylePosition* position = (nsStylePosition*)mStyleContext->GetMutableStyleData(eStyleStruct_Position);
if (position->mWidth.GetUnit() == eStyleUnit_Coord) {
if (0 >= position->mWidth.GetCoordValue()) {
makeAuto= PR_TRUE;
}
}
else if (position->mWidth.GetUnit() == eStyleUnit_Percent) {
if (0.0f >= position->mWidth.GetPercentValue()) {
makeAuto= PR_TRUE;
}
}
if (makeAuto) {
position->mWidth = nsStyleCoord(eStyleUnit_Auto);
}
return NS_OK;
}
NS_IMETHODIMP
nsTableOuterFrame::Init(nsIPresContext* aPresContext,
nsIContent* aContent,
nsIFrame* aParent,
nsIStyleContext* aContext,
nsIFrame* aPrevInFlow)
{
nsresult rv = nsHTMLContainerFrame::Init(aPresContext, aContent, aParent,
aContext, aPrevInFlow);
if (NS_FAILED(rv) || !mStyleContext) return rv;
AdjustZeroWidth();
return rv;
}
NS_IMETHODIMP
nsTableOuterFrame::FirstChild(nsIPresContext* aPresContext,
nsIAtom* aListName,
nsIFrame** aFirstChild) const
{
NS_PRECONDITION(nsnull != aFirstChild, "null OUT parameter pointer");
*aFirstChild = (nsLayoutAtoms::captionList == aListName)
? mCaptionFrame : mFrames.FirstChild();
return NS_OK;
}
NS_IMETHODIMP
nsTableOuterFrame::GetAdditionalChildListName(PRInt32 aIndex,
nsIAtom** aListName) const
{
NS_PRECONDITION(nsnull != aListName, "null OUT parameter pointer");
if (aIndex < 0) {
return NS_ERROR_INVALID_ARG;
}
*aListName = nsnull;
switch (aIndex) {
case NS_TABLE_FRAME_CAPTION_LIST_INDEX:
*aListName = nsLayoutAtoms::captionList;
NS_ADDREF(*aListName);
break;
}
return NS_OK;
}
NS_IMETHODIMP
nsTableOuterFrame::SetInitialChildList(nsIPresContext* aPresContext,
nsIAtom* aListName,
nsIFrame* aChildList)
{
if (nsLayoutAtoms::captionList == aListName) {
// the frame constructor already checked for table-caption display type
mCaptionFrame = aChildList;
}
else {
mFrames.SetFrames(aChildList);
mInnerTableFrame = aChildList;
}
return NS_OK;
}
NS_IMETHODIMP
nsTableOuterFrame::AppendFrames(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aFrameList)
{
nsresult rv;
// We only have two child frames: the inner table and one caption frame.
// The inner frame is provided when we're initialized, and it cannot change
if (nsLayoutAtoms::captionList == aListName) {
NS_PRECONDITION(!mCaptionFrame, "already have a caption frame");
// We only support having a single caption frame
if (mCaptionFrame || (LengthOf(aFrameList) > 1)) {
rv = NS_ERROR_UNEXPECTED;
} else {
// Insert the caption frame into the child list
mCaptionFrame = aFrameList;
// Reflow the new caption frame. It's already marked dirty, so generate a reflow
// command that tells us to reflow our dirty child frames
nsIReflowCommand* reflowCmd;
rv = NS_NewHTMLReflowCommand(&reflowCmd, this, nsIReflowCommand::ReflowDirty);
if (NS_SUCCEEDED(rv)) {
aPresShell.AppendReflowCommand(reflowCmd);
NS_RELEASE(reflowCmd);
}
}
}
else {
NS_PRECONDITION(PR_FALSE, "unexpected child frame type");
rv = NS_ERROR_UNEXPECTED;
}
return rv;
}
NS_IMETHODIMP
nsTableOuterFrame::InsertFrames(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aPrevFrame,
nsIFrame* aFrameList)
{
NS_PRECONDITION(!aPrevFrame, "invalid previous frame");
return AppendFrames(aPresContext, aPresShell, aListName, aFrameList);
}
NS_IMETHODIMP
nsTableOuterFrame::RemoveFrame(nsIPresContext* aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aOldFrame)
{
nsresult rv;
// We only have two child frames: the inner table and one caption frame.
// The inner frame can't be removed so this should be the caption
NS_PRECONDITION(nsLayoutAtoms::captionList == aListName, "can't remove inner frame");
NS_PRECONDITION(aOldFrame == mCaptionFrame, "invalid caption frame");
// See if the caption's minimum width impacted the inner table
if (mMinCaptionWidth > mRect.width) {
// The old caption width had an effect on the inner table width so
// we're going to need to reflow it. Mark it dirty
nsFrameState frameState;
mInnerTableFrame->GetFrameState(&frameState);
frameState |= NS_FRAME_IS_DIRTY;
mInnerTableFrame->SetFrameState(frameState);
}
// Remove the caption frame and destroy it
if (mCaptionFrame && (mCaptionFrame == aOldFrame)) {
mCaptionFrame->Destroy(aPresContext);
mCaptionFrame = nsnull;
mMinCaptionWidth = 0;
}
// Generate a reflow command so we get reflowed
nsIReflowCommand* reflowCmd;
rv = NS_NewHTMLReflowCommand(&reflowCmd, this, nsIReflowCommand::ReflowDirty);
if (NS_SUCCEEDED(rv)) {
aPresShell.AppendReflowCommand(reflowCmd);
NS_RELEASE(reflowCmd);
}
return NS_OK;
}
NS_METHOD nsTableOuterFrame::Paint(nsIPresContext* aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsFramePaintLayer aWhichLayer)
{
#ifdef DEBUG
// for debug...
if ((NS_FRAME_PAINT_LAYER_DEBUG == aWhichLayer) && GetShowFrameBorders()) {
aRenderingContext.SetColor(NS_RGB(255,0,0));
aRenderingContext.DrawRect(0, 0, mRect.width, mRect.height);
}
#endif
PRBool isVisible;
if (NS_SUCCEEDED(IsVisibleForPainting(aPresContext, aRenderingContext, PR_FALSE, &isVisible)) && !isVisible) {
return NS_OK;
}
// the remaining code was copied from nsContainerFrame::PaintChildren since
// it only paints the primary child list
const nsStyleDisplay* disp = (const nsStyleDisplay*)
mStyleContext->GetStyleData(eStyleStruct_Display);
// Child elements have the opportunity to override the visibility property
// of their parent and display even if the parent is hidden
PRBool clipState;
// If overflow is hidden then set the clip rect so that children
// don't leak out of us
if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) {
aRenderingContext.PushState();
aRenderingContext.SetClipRect(nsRect(0, 0, mRect.width, mRect.height),
nsClipCombine_kIntersect, clipState);
}
if (mCaptionFrame) {
PaintChild(aPresContext, aRenderingContext, aDirtyRect, mCaptionFrame, aWhichLayer);
}
nsIFrame* kid = mFrames.FirstChild();
while (nsnull != kid) {
PaintChild(aPresContext, aRenderingContext, aDirtyRect, kid, aWhichLayer);
kid->GetNextSibling(&kid);
}
if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) {
aRenderingContext.PopState(clipState);
}
return NS_OK;
}
NS_IMETHODIMP
nsTableOuterFrame::GetFrameForPoint(nsIPresContext* aPresContext,
const nsPoint& aPoint,
nsFramePaintLayer aWhichLayer,
nsIFrame** aFrame)
{
nsresult rv;
// caption frames live in a different list which we need to check separately
if (mCaptionFrame) {
rv = GetFrameForPointUsing(aPresContext, aPoint, nsLayoutAtoms::captionList, NS_FRAME_PAINT_LAYER_FOREGROUND, PR_FALSE, aFrame);
if (NS_OK == rv) {
return NS_OK;
}
}
// this should act like a block, so we need to override
return GetFrameForPointUsing(aPresContext, aPoint, nsnull, aWhichLayer, (aWhichLayer == NS_FRAME_PAINT_LAYER_BACKGROUND), aFrame);
}
NS_IMETHODIMP nsTableOuterFrame::SetSelected(nsIPresContext* aPresContext,
nsIDOMRange *aRange,
PRBool aSelected,
nsSpread aSpread)
{
nsresult result = nsFrame::SetSelected(aPresContext, aRange,aSelected, aSpread);
if (NS_SUCCEEDED(result) && mInnerTableFrame)
return mInnerTableFrame->SetSelected(aPresContext, aRange,aSelected, aSpread);
return result;
}
PRBool nsTableOuterFrame::NeedsReflow(const nsHTMLReflowState& aReflowState)
{
PRBool result=PR_TRUE;
if (nsnull != mInnerTableFrame) {
result = ((nsTableFrame *)mInnerTableFrame)->NeedsReflow(aReflowState);
}
return result;
}
// GetParentStyleContextProvider:
// The innerTableFame is he parent style context provider
// Fortunately, we cache that as a data member, so just return the cached pointer value
//
NS_IMETHODIMP
nsTableOuterFrame::GetParentStyleContextProvider(nsIPresContext* aPresContext,
nsIFrame** aProviderFrame,
nsContextProviderRelationship& aRelationship)
{
NS_ASSERTION(aProviderFrame && aPresContext, "null argument: aPresContext and-or aProviderFrame");
if (aProviderFrame) {
// parent context provider is the innerTableFrame
NS_ASSERTION(mInnerTableFrame, "innerTableFrame should not be null");
*aProviderFrame = mInnerTableFrame;
aRelationship = eContextProvider_Descendant;
}
return ((aProviderFrame != nsnull) && (*aProviderFrame != nsnull)) ? NS_OK : NS_ERROR_FAILURE;
}
// INCREMENTAL REFLOW HELPER FUNCTIONS
nsSize
GetFrameSize(nsIFrame& aFrame)
{
nsRect rect;
aFrame.GetRect(rect);
nsSize size(rect.width, rect.height);
return size;
}
void
nsTableOuterFrame::ZeroAutoMargin(nsHTMLReflowState& aReflowState,
nsMargin& aMargin)
{
if (eStyleUnit_Auto == aReflowState.mStyleSpacing->mMargin.GetLeftUnit()) {
aMargin.left = 0;
}
if (eStyleUnit_Auto == aReflowState.mStyleSpacing->mMargin.GetRightUnit()) {
aMargin.right = 0;
}
}
void
FixAutoMargins(nscoord aAvailWidth,
nsHTMLReflowState& aReflowState)
{
// see if there are auto margins. they may have been set to 0 in mComputedMargin
PRBool hasAutoMargin = eStyleUnit_Auto == aReflowState.mStyleSpacing->mMargin.GetLeftUnit() ||
eStyleUnit_Auto == aReflowState.mStyleSpacing->mMargin.GetRightUnit();
if (hasAutoMargin) {
nsRect rect;
aReflowState.frame->GetRect(rect);
nscoord compWidth = rect.width - aReflowState.mComputedBorderPadding.left -
aReflowState.mComputedBorderPadding.right;
aReflowState.CalculateBlockSideMargins(aAvailWidth, compWidth);
}
}
// get the margin and padding data. nsHTMLReflowState doesn't handle the
// case of auto margins
void
GetMarginPadding(nsIPresContext* aPresContext,
const nsHTMLReflowState& aOuterRS,
nsIFrame* aChildFrame,
nsMargin& aMargin,
nsMargin& aMarginNoAuto,
nsMargin& aPadding)
{
// construct a reflow state to compute margin and padding. Auto margins
// will not be computed at this time.
nsHTMLReflowState childRS(aPresContext, aOuterRS, aChildFrame,
nsSize(aOuterRS.availableWidth, aOuterRS.availableHeight),
eReflowReason_Resize);
FixAutoMargins(aOuterRS.availableWidth, childRS);
aMargin = childRS.mComputedMargin;
aMarginNoAuto = aMargin;
nsTableOuterFrame::ZeroAutoMargin(childRS, aMarginNoAuto);
aPadding = childRS.mComputedPadding;
}
nscoord CalcAutoMargin(nscoord aAutoMargin,
nscoord aOppositeMargin,
nscoord aContainBlockSize,
nscoord aFrameSize)
{
nscoord margin;
if (NS_AUTOMARGIN == aOppositeMargin) {
margin = (aContainBlockSize - aFrameSize) / 2;
}
else {
margin = aContainBlockSize - aFrameSize - aOppositeMargin;
}
return PR_MAX(0, margin);
}
nscoord
nsTableOuterFrame::GetChildAvailWidth(nsIPresContext* aPresContext,
nsIFrame* aChildFrame,
const nsHTMLReflowState& aOuterRS,
nscoord aOuterWidth,
nsMargin& aMarginNoAuto,
nsMargin& aPadding)
{
if (NS_UNCONSTRAINEDSIZE == aOuterWidth) return aOuterWidth;
nsMargin marginIgnore;
GetMarginPadding(aPresContext, aOuterRS, aChildFrame, marginIgnore, aMarginNoAuto, aPadding);
nscoord width = aOuterWidth;
if (NS_UNCONSTRAINEDSIZE != width) {
width = aOuterWidth - aMarginNoAuto.left + aMarginNoAuto.right;
width = PR_MAX(width, mMinCaptionWidth);
}
return width;
}
void
MoveFrameTo(nsIPresContext* aPresContext,
nsIFrame* aFrame,
nscoord aX,
nscoord aY)
{
nsRect oldRect;
aFrame->GetRect(oldRect);
if ((oldRect.x != aX) || (oldRect.y != aY)) {
aFrame->MoveTo(aPresContext, aX, aY);
nsTableOuterFrame::PositionView(aPresContext, aFrame);
}
}
nsSize
GetContainingBlockSize(const nsHTMLReflowState& aOuterRS)
{
nsSize size(0,0);
const nsHTMLReflowState* containRS =
nsHTMLReflowState::GetContainingBlockReflowState(aOuterRS.parentReflowState);
if (containRS) {
size.width = containRS->mComputedWidth;
if (NS_UNCONSTRAINEDSIZE == size.width) {
size.width = 0;
}
size.height = containRS->mComputedHeight;
if (NS_UNCONSTRAINEDSIZE == size.height) {
size.height = 0;
}
}
return size;
}
void
nsTableOuterFrame::InvalidateDamage(nsIPresContext* aPresContext,
PRUint8 aCaptionSide,
nsSize& aOuterSize,
PRBool aInnerChanged,
PRBool aCaptionChanged)
{
if (!aInnerChanged && !aCaptionChanged) return;
nsRect damage;
if (aInnerChanged && aCaptionChanged) {
damage = nsRect(0, 0, aOuterSize.width, aOuterSize.height);
}
else {
nsRect innerRect, captionRect(0,0,0,0);
mInnerTableFrame->GetRect(innerRect);
if (mCaptionFrame) {
mCaptionFrame->GetRect(captionRect);
}
// only works for vertical captions {
damage.x = 0;
damage.width = aOuterSize.width;
switch(aCaptionSide) {
case NS_SIDE_BOTTOM:
if (aCaptionChanged) {
damage.y = innerRect.y;
damage.height = aOuterSize.height - damage.y;
}
else {
damage.y = 0;
damage.height = captionRect.y;
}
break;
default:
if (aCaptionChanged) {
damage.y = 0;
damage.height = innerRect.y;
}
else {
damage.y = captionRect.y;
damage.height = aOuterSize.height - damage.y;
}
break;
}
}
Invalidate(aPresContext, damage);
}
nscoord
nsTableOuterFrame::GetCaptionAvailWidth(nsIPresContext* aPresContext,
nsIFrame* aCaptionFrame,
const nsHTMLReflowState& aOuterRS,
nscoord* aInnerWidth,
const nsMargin* aInnerMarginNoAuto)
{
nscoord outerWidth;
if (aInnerWidth) {
nscoord innerWidth = *aInnerWidth;
if (NS_UNCONSTRAINEDSIZE == innerWidth) {
outerWidth = innerWidth;
}
else {
nsMargin innerMargin(0,0,0,0);
if (aInnerMarginNoAuto) {
innerMargin = *aInnerMarginNoAuto;
}
outerWidth = innerWidth + innerMargin.left + innerMargin.right;
}
}
else {
nsSize outerSize = GetFrameSize(*this);
outerWidth = outerSize.width;
}
if (NS_UNCONSTRAINEDSIZE == outerWidth) {
return outerWidth;
}
else {
nsMargin capMargin, capPad;
return GetChildAvailWidth(aPresContext, aCaptionFrame, aOuterRS,
outerWidth, capMargin, capPad);
}
}
nsSize
nsTableOuterFrame::GetMaxElementSize(const nsMargin& aInnerMargin,
const nsMargin& aInnerPadding,
const nsMargin& aCaptionMargin)
{
nsSize size;
((nsTableFrame *)mInnerTableFrame)->SetMaxElementSize(&size, aInnerPadding);
size.width += aInnerMargin.left + aInnerMargin.right;
if (mCaptionFrame) {
nscoord capWidth = mMinCaptionWidth + aCaptionMargin.left + aCaptionMargin.right;
if (capWidth > size.width) {
size.width = capWidth;
}
}
size.height = 0; // max element height is not used for anything is it?
return size;
}
nscoord
nsTableOuterFrame::GetMaxWidth(PRUint8 aCaptionSide,
const nsMargin& aInnerMargin,
const nsMargin& aCaptionMargin)
{
nscoord maxWidth;
switch(aCaptionSide) {
case NS_SIDE_TOP:
case NS_SIDE_BOTTOM:
case NS_SIDE_LEFT:
case NS_SIDE_RIGHT:
default: // no caption
maxWidth = mInnerTableMaximumWidth + aInnerMargin.left + aInnerMargin.right;
if (mCaptionFrame) {
maxWidth = PR_MAX(maxWidth, mMinCaptionWidth + aCaptionMargin.left + aCaptionMargin.right);
}
}
return maxWidth;
}
PRUint8
nsTableOuterFrame::GetCaptionSide()
{
const nsStyleTable* tableStyle;
if (mCaptionFrame) {
mCaptionFrame->GetStyleData(eStyleStruct_Table, ((const nsStyleStruct *&)tableStyle));
return tableStyle->mCaptionSide;
}
else {
return NO_SIDE; // no caption
}
}
void
nsTableOuterFrame::SetDesiredSize(PRUint8 aCaptionSide,
const nsMargin& aInnerMargin,
const nsMargin& aCaptionMargin,
nscoord& aWidth,
nscoord& aHeight)
{
aWidth = aHeight = 0;
nsRect innerRect;
mInnerTableFrame->GetRect(innerRect);
nsRect captionRect(0,0,0,0);
if (mCaptionFrame) {
mCaptionFrame->GetRect(captionRect);
}
switch(aCaptionSide) {
case NS_SIDE_TOP:
case NS_SIDE_BOTTOM:
case NS_SIDE_LEFT:
case NS_SIDE_RIGHT:
default: // no caption
aWidth = innerRect.XMost() + aInnerMargin.right;
aWidth = PR_MAX(aWidth, captionRect.XMost() + aCaptionMargin.right);
break;
}
switch(aCaptionSide) {
case NS_SIDE_BOTTOM:
aHeight = captionRect.YMost() + aCaptionMargin.bottom;
break;
default: // top caption or no caption
aHeight = innerRect.YMost() + aInnerMargin.bottom;
}
}
nsresult
nsTableOuterFrame::GetCaptionOrigin(nsIPresContext* aPresContext,
PRUint32 aCaptionSide,
const nsSize& aContainBlockSize,
const nsSize& aInnerSize,
const nsMargin& aInnerMargin,
const nsSize& aCaptionSize,
nsMargin& aCaptionMargin,
nsPoint& aOrigin)
{
aOrigin.x = aOrigin.y = 0;
if ((NS_UNCONSTRAINEDSIZE == aInnerSize.width) || (NS_UNCONSTRAINEDSIZE == aInnerSize.width) ||
(NS_UNCONSTRAINEDSIZE == aCaptionSize.width) || (NS_UNCONSTRAINEDSIZE == aCaptionSize.width)) {
return NS_OK;
}
if (!mCaptionFrame) return NS_OK;
nscoord collapseMargin;
switch(aCaptionSide) {
case NS_SIDE_BOTTOM:
if (NS_AUTOMARGIN == aCaptionMargin.left) {
aCaptionMargin.left = CalcAutoMargin(aCaptionMargin.left, aCaptionMargin.right,
aContainBlockSize.width, aCaptionSize.width);
}
aOrigin.x = aCaptionMargin.left;
if (NS_AUTOMARGIN == aCaptionMargin.top) {
aCaptionMargin.top = 0;
}
collapseMargin = PR_MAX(aCaptionMargin.top, aInnerMargin.bottom);
if (NS_AUTOMARGIN == aCaptionMargin.bottom) {
nscoord height = aInnerSize.height + collapseMargin + aCaptionSize.height;
aCaptionMargin.bottom = CalcAutoMargin(aCaptionMargin.bottom, aInnerMargin.top,
aContainBlockSize.height, height);
}
aOrigin.y = aInnerMargin.top + aInnerSize.height + collapseMargin;
break;
default: // all others are treated as top for now
if (NS_AUTOMARGIN == aCaptionMargin.left) {
aCaptionMargin.left = CalcAutoMargin(aCaptionMargin.left, aCaptionMargin.right,
aContainBlockSize.width, aCaptionSize.width);
}
aOrigin.x = aCaptionMargin.left;
if (NS_AUTOMARGIN == aCaptionMargin.bottom) {
aCaptionMargin.bottom = 0;
}
if (NS_AUTOMARGIN == aCaptionMargin.top) {
collapseMargin = PR_MAX(aCaptionMargin.bottom, aInnerMargin.top);
nscoord height = aCaptionSize.height + collapseMargin + aInnerSize.height;
aCaptionMargin.top = CalcAutoMargin(aCaptionMargin.top, aInnerMargin.bottom,
aContainBlockSize.height, height);
}
aOrigin.y = aCaptionMargin.top;
break;
}
return NS_OK;
}
nsresult
nsTableOuterFrame::GetInnerOrigin(nsIPresContext* aPresContext,
PRUint32 aCaptionSide,
const nsSize& aContainBlockSize,
const nsSize& aCaptionSize,
const nsMargin& aCaptionMargin,
const nsSize& aInnerSize,
nsMargin& aInnerMargin,
nsPoint& aOrigin)
{
aOrigin.x = aOrigin.y = 0;
if ((NS_UNCONSTRAINEDSIZE == aInnerSize.width) || (NS_UNCONSTRAINEDSIZE == aInnerSize.width) ||
(NS_UNCONSTRAINEDSIZE == aCaptionSize.width) || (NS_UNCONSTRAINEDSIZE == aCaptionSize.width)) {
return NS_OK;
}
nscoord collapseMargin;
switch(aCaptionSide) {
case NS_SIDE_BOTTOM:
if (NS_AUTOMARGIN == aInnerMargin.left) {
aInnerMargin.left = CalcAutoMargin(aInnerMargin.left, aInnerMargin.right,
aContainBlockSize.width, aInnerSize.width);
}
aOrigin.x = aInnerMargin.left;
if (NS_AUTOMARGIN == aInnerMargin.bottom) {
aInnerMargin.bottom = 0;
}
if (NS_AUTOMARGIN == aInnerMargin.top) {
collapseMargin = PR_MAX(aInnerMargin.bottom, aCaptionMargin.top);
nscoord height = aInnerSize.height + collapseMargin + aCaptionSize.height;
aInnerMargin.top = CalcAutoMargin(aInnerMargin.top, aCaptionMargin.bottom,
aContainBlockSize.height, height);
}
aOrigin.y = aInnerMargin.top;
break;
default: // all others are treated as top for now
if (NS_AUTOMARGIN == aInnerMargin.left) {
aInnerMargin.left = CalcAutoMargin(aInnerMargin.left, aInnerMargin.right,
aContainBlockSize.width, aInnerSize.width);
}
aOrigin.x = aInnerMargin.left;
if (NS_AUTOMARGIN == aInnerMargin.top) {
aInnerMargin.top = 0;
}
collapseMargin = PR_MAX(aCaptionMargin.bottom, aInnerMargin.top);
if (NS_AUTOMARGIN == aInnerMargin.bottom) {
nscoord height = aCaptionSize.height + collapseMargin + aInnerSize.height;
aInnerMargin.bottom = CalcAutoMargin(aCaptionMargin.bottom, aInnerMargin.top,
aContainBlockSize.height, height);
}
aOrigin.y = aCaptionMargin.top + aCaptionSize.height + collapseMargin;
break;
}
return NS_OK;
}
// eReflowReason_Resize was being used for incremental cases
nsresult
nsTableOuterFrame::OuterReflowChild(nsIPresContext* aPresContext,
nsIFrame* aChildFrame,
const nsHTMLReflowState& aOuterRS,
nsHTMLReflowMetrics& aMetrics,
nscoord* aAvailWidth,
nsSize& aDesiredSize,
nsMargin& aMargin,
nsMargin& aMarginNoAuto,
nsMargin& aPadding,
nsReflowReason aReflowReason,
nsReflowStatus& aStatus)
{
aMargin = aPadding = nsMargin(0,0,0,0);
nscoord availWidth = GetChildAvailWidth(aPresContext, aChildFrame, aOuterRS,
aOuterRS.availableWidth, aMargin, aPadding);
if (aAvailWidth) {
availWidth = *aAvailWidth;
}
nsHTMLReflowState childRS(aPresContext, aOuterRS, aChildFrame,
nsSize(availWidth, aOuterRS.availableHeight));
childRS.reason = aReflowReason;
// Normally, the outer table's mComputed values are NS_INTRINSICSIZE (although to
// to work around boxes they can also be set to 0) since they depend on the caption
// and inner table. Boxes can force a constrained size.
// XXX remove this code when trees are converted to use grids instead of tables
if ((aOuterRS.mComputedWidth != NS_INTRINSICSIZE) &&
(aOuterRS.mComputedWidth != 0)) {
if (mInnerTableFrame == aChildFrame) {
childRS.mComputedWidth = aOuterRS.mComputedWidth - aMargin.left - childRS.mComputedBorderPadding.left -
childRS.mComputedBorderPadding.right - aMargin.right;
childRS.mComputedWidth = PR_MAX(0, childRS.mComputedWidth);
}
else {
NS_ASSERTION(PR_FALSE, "box set mComputedWidth on an outer table with a caption");
}
}
if ((aOuterRS.mComputedHeight != NS_INTRINSICSIZE) &&
(aOuterRS.mComputedHeight != 0)) {
if (mInnerTableFrame == aChildFrame) {
childRS.mComputedHeight = aOuterRS.mComputedHeight - aMargin.top - childRS.mComputedBorderPadding.top -
childRS.mComputedBorderPadding.bottom - aMargin.bottom;
childRS.mComputedHeight = PR_MAX(0, childRS.mComputedHeight);
}
else {
NS_ASSERTION(PR_FALSE, "box set mComputedHeight on an outer table with a caption");
}
}
// use the current position as a best guess for placement
nsRect childRect;
aChildFrame->GetRect(childRect);
nsresult rv = ReflowChild(aChildFrame, aPresContext, aMetrics, childRS,
childRect.x, childRect.y, NS_FRAME_NO_MOVE_FRAME, aStatus);
if (NS_FAILED(rv)) return rv;
FixAutoMargins(aOuterRS.availableWidth, childRS);
aMargin = childRS.mComputedMargin;
aMarginNoAuto = childRS.mComputedMargin;
ZeroAutoMargin(childRS, aMarginNoAuto);
aDesiredSize.width = aMetrics.width;
aDesiredSize.height = aMetrics.height;
return rv;
}
void
nsTableOuterFrame::UpdateReflowMetrics(PRUint8 aCaptionSide,
nsHTMLReflowMetrics& aMet,
const nsMargin& aInnerMargin,
const nsMargin& aInnerMarginNoAuto,
const nsMargin& aInnerPadding,
const nsMargin& aCaptionMargin,
const nsMargin& aCaptionMarginNoAuto)
{
SetDesiredSize(aCaptionSide, aInnerMargin, aCaptionMargin, aMet.width, aMet.height);
// set maxElementSize width if requested
if (aMet.maxElementSize) {
*aMet.maxElementSize = GetMaxElementSize(aInnerMarginNoAuto, aInnerPadding, aCaptionMarginNoAuto);
}
// set maximum width if requested
if (aMet.mFlags & NS_REFLOW_CALC_MAX_WIDTH) {
aMet.mMaximumWidth = GetMaxWidth(aCaptionSide, aInnerMarginNoAuto, aCaptionMarginNoAuto);
}
}
nsresult
nsTableOuterFrame::IncrementalReflow(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
nsresult rv = NS_OK;
// determine if this frame is the target or not
nsIFrame* target=nsnull;
rv = aReflowState.reflowCommand->GetTarget(target);
if (NS_SUCCEEDED(rv) && target) {
if (this == target) {
rv = IR_TargetIsMe(aPresContext, aDesiredSize, aReflowState, aStatus);
}
else {
// Get the next frame in the reflow chain
nsIFrame* nextFrame;
aReflowState.reflowCommand->GetNext(nextFrame);
NS_ASSERTION(nextFrame, "next frame in reflow command is null");
rv = IR_TargetIsChild(aPresContext, aDesiredSize, aReflowState, aStatus, nextFrame);
}
}
return rv;
}
nsresult
nsTableOuterFrame::IR_TargetIsChild(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus,
nsIFrame* aNextFrame)
{
nsresult rv;
if (!aNextFrame) {
// this will force Reflow to return the height of the last reflow rather than 0
aDesiredSize.height = mRect.height;
return NS_OK;
}
if (aNextFrame == mInnerTableFrame) {
rv = IR_TargetIsInnerTableFrame(aPresContext, aDesiredSize, aReflowState, aStatus);
}
else if (aNextFrame == mCaptionFrame) {
rv = IR_TargetIsCaptionFrame(aPresContext, aDesiredSize, aReflowState, aStatus);
}
else {
const nsStyleDisplay* nextDisplay;
aNextFrame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct *&)nextDisplay);
if (NS_STYLE_DISPLAY_TABLE_HEADER_GROUP==nextDisplay->mDisplay ||
NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP==nextDisplay->mDisplay ||
NS_STYLE_DISPLAY_TABLE_ROW_GROUP ==nextDisplay->mDisplay ||
NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP==nextDisplay->mDisplay) {
rv = IR_TargetIsInnerTableFrame(aPresContext, aDesiredSize, aReflowState, aStatus);
}
else {
NS_ASSERTION(PR_FALSE, "illegal next frame in incremental reflow.");
rv = NS_ERROR_ILLEGAL_VALUE;
}
}
return rv;
}
nsresult
nsTableOuterFrame::IR_TargetIsInnerTableFrame(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
nsresult rv = IR_InnerTableReflow(aPresContext, aDesiredSize, aReflowState, aStatus);
return rv;
}
nsresult
nsTableOuterFrame::IR_TargetIsCaptionFrame(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aOuterRS,
nsReflowStatus& aStatus)
{
nsresult rv = NS_OK;
aStatus = NS_FRAME_COMPLETE;
PRUint8 captionSide = GetCaptionSide();
nsSize captionSize, captionMES;
nsMargin captionMargin, captionMarginNoAuto, captionPadding;
// reflow the caption frame, getting it's MES
nscoord availWidth = GetCaptionAvailWidth(aPresContext, mCaptionFrame, aOuterRS);
nsHTMLReflowMetrics captionMet(&captionMES);
nsReflowStatus capStatus; // don't let the caption cause incomplete
OuterReflowChild(aPresContext, mCaptionFrame, aOuterRS, captionMet, &availWidth, captionSize,
captionMargin, captionMarginNoAuto, captionPadding, eReflowReason_Incremental, capStatus);
nsMargin innerMargin, innerMarginNoAuto, innerPadding;
nsPoint innerOrigin;
nsSize containSize = GetContainingBlockSize(aOuterRS);
// for now just reflow the table if a style changed. This should be improved
nsIReflowCommand::ReflowType reflowCommandType;
aOuterRS.reflowCommand->GetType(reflowCommandType);
PRBool needInnerReflow = (nsIReflowCommand::StyleChanged == reflowCommandType)
? PR_TRUE : PR_FALSE;
if (mMinCaptionWidth != captionMES.width) {
// set the new caption min width, and set state to reflow the inner table if necessary
mMinCaptionWidth = captionMES.width;
// see if the captions min width could cause the table to be wider
// XXX this really only affects an auto width table
if ((mMinCaptionWidth + captionMarginNoAuto.left + captionMarginNoAuto.right) > mRect.width) {
needInnerReflow = PR_TRUE;
}
}
nsRect prevInnerRect;
mInnerTableFrame->GetRect(prevInnerRect);
nsPoint captionOrigin;
if (needInnerReflow) {
nsSize innerSize;
nsHTMLReflowMetrics innerMet(nsnull);
OuterReflowChild(aPresContext, mInnerTableFrame, aOuterRS, innerMet, nsnull, innerSize,
innerMargin, innerMarginNoAuto, innerPadding, eReflowReason_Resize, aStatus);
GetInnerOrigin(aPresContext, captionSide, containSize, captionSize,
captionMargin, innerSize, innerMargin, innerOrigin);
rv = FinishReflowChild(mInnerTableFrame, aPresContext, innerMet,
innerOrigin.x, innerOrigin.y, 0);
if (NS_FAILED(rv)) return rv;
GetCaptionOrigin(aPresContext, captionSide, containSize, innerSize,
innerMargin, captionSize, captionMargin, captionOrigin);
}
else {
// reposition the inner frame if necessary and set the caption's origin
nsSize innerSize = GetFrameSize(*mInnerTableFrame);
GetMarginPadding(aPresContext, aOuterRS, mInnerTableFrame, innerMargin,
innerMarginNoAuto, innerPadding);
GetInnerOrigin(aPresContext, captionSide, containSize, captionSize,
captionMargin, innerSize, innerMargin, innerOrigin);
GetCaptionOrigin(aPresContext, captionSide, containSize, innerSize,
innerMargin, captionSize, captionMargin, captionOrigin);
MoveFrameTo(aPresContext, mInnerTableFrame, innerOrigin.x, innerOrigin.y);
}
rv = FinishReflowChild(mCaptionFrame, aPresContext, captionMet,
captionOrigin.x, captionOrigin.y, 0);
UpdateReflowMetrics(captionSide, aDesiredSize, innerMargin, innerMarginNoAuto,
innerPadding, captionMargin, captionMarginNoAuto);
nsSize desSize(aDesiredSize.width, aDesiredSize.height);
PRBool innerMoved = (innerOrigin.x != prevInnerRect.x) || (innerOrigin.y != prevInnerRect.y);
InvalidateDamage(aPresContext, captionSide, desSize, innerMoved, PR_TRUE);
return rv;
}
nsresult
nsTableOuterFrame::IR_ReflowDirty(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
nsFrameState frameState;
nsresult rv;
PRBool sizeSet = PR_FALSE;
// See if the caption frame is dirty. This would be because of a newly
// inserted caption
if (mCaptionFrame) {
mCaptionFrame->GetFrameState(&frameState);
if (frameState & NS_FRAME_IS_DIRTY) {
rv = IR_CaptionInserted(aPresContext, aDesiredSize, aReflowState, aStatus);
sizeSet = PR_TRUE;
}
}
// See if the inner table frame is dirty
mInnerTableFrame->GetFrameState(&frameState);
if (frameState & NS_FRAME_IS_DIRTY) {
rv = IR_InnerTableReflow(aPresContext, aDesiredSize, aReflowState, aStatus);
sizeSet = PR_TRUE;
}
else if (!mCaptionFrame) {
// The inner table isn't dirty so we don't need to reflow it, but make
// sure it's placed correctly. It could be that we're dirty because the
// caption was removed
nsRect innerRect;
mInnerTableFrame->GetRect(innerRect);
nsSize innerSize(innerRect.width, innerRect.height);
nsPoint innerOrigin;
nsMargin innerMargin, innerMarginNoAuto, innerPadding;
GetMarginPadding(aPresContext, aReflowState, mInnerTableFrame, innerMargin,
innerMarginNoAuto, innerPadding);
nsSize containSize = GetContainingBlockSize(aReflowState);
GetInnerOrigin(aPresContext, NO_SIDE, containSize, nsSize(0,0),
nsMargin(0,0,0,0), innerSize, innerMargin, innerOrigin);
MoveFrameTo(aPresContext, mInnerTableFrame, innerOrigin.x, innerOrigin.y);
aDesiredSize.width = innerRect.XMost() + innerMargin.right;
aDesiredSize.height = innerRect.YMost() + innerMargin.bottom;
sizeSet = PR_TRUE;
// Repaint the inner's entire bounds if it moved
if ((innerRect.x != innerOrigin.x) || (innerRect.y != innerOrigin.y)) {
Invalidate(aPresContext, nsRect(0, 0, aDesiredSize.width, aDesiredSize.height));
}
}
if (!sizeSet) {
// set our desired size to what it was before
nsSize size = GetFrameSize(*this);
aDesiredSize.width = size.width;
aDesiredSize.height = size.height;
}
return rv;
}
// IR_TargetIsMe is free to foward the request to the inner table frame
nsresult nsTableOuterFrame::IR_TargetIsMe(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
nsresult rv = NS_OK;
nsIReflowCommand::ReflowType type;
aReflowState.reflowCommand->GetType(type);
nsIFrame* objectFrame;
aReflowState.reflowCommand->GetChildFrame(objectFrame);
switch (type) {
case nsIReflowCommand::ReflowDirty:
rv = IR_ReflowDirty(aPresContext, aDesiredSize, aReflowState, aStatus);
break;
case nsIReflowCommand::StyleChanged :
rv = IR_InnerTableReflow(aPresContext, aDesiredSize, aReflowState, aStatus);
break;
case nsIReflowCommand::ContentChanged :
NS_ASSERTION(PR_FALSE, "illegal reflow type: ContentChanged");
rv = NS_ERROR_ILLEGAL_VALUE;
break;
default:
NS_NOTYETIMPLEMENTED("unexpected reflow command type");
rv = NS_ERROR_NOT_IMPLEMENTED;
break;
}
return rv;
}
nsresult
nsTableOuterFrame::IR_InnerTableReflow(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aOuterMet,
const nsHTMLReflowState& aOuterRS,
nsReflowStatus& aStatus)
{
aStatus = NS_FRAME_COMPLETE;
PRUint8 captionSide = GetCaptionSide();
nsRect priorInnerRect;
mInnerTableFrame->GetRect(priorInnerRect);
nsSize innerSize;
nsMargin innerMargin, innerMarginNoAuto, innerPadding;
// pass along the reflow command to the inner table
nsHTMLReflowMetrics innerMet(aOuterMet.maxElementSize);
// request the maximum of the inner table if requested of the outer
if ((aOuterMet.mFlags & NS_REFLOW_CALC_MAX_WIDTH) &&
((nsTableFrame*)mInnerTableFrame)->IsAutoLayout()) {
innerMet.mFlags |= NS_REFLOW_CALC_MAX_WIDTH;
}
nsresult rv = OuterReflowChild(aPresContext, mInnerTableFrame, aOuterRS, innerMet,
nsnull, innerSize, innerMargin, innerMarginNoAuto, innerPadding,
eReflowReason_Incremental, aStatus);
if (NS_FAILED(rv)) return rv;
if (innerMet.mFlags & NS_REFLOW_CALC_MAX_WIDTH) {
mInnerTableMaximumWidth = innerMet.mMaximumWidth;
}
nsPoint innerOrigin(0,0);
nsMargin captionMargin(0,0,0,0);
nsMargin captionMarginNoAuto(0,0,0,0);
nsSize captionSize(0,0);
nsSize containSize = GetContainingBlockSize(aOuterRS);
PRBool captionMoved = PR_FALSE;
// if there is a caption and the width or height of the inner table changed
// from a reflow, then reflow or move the caption as needed
if (mCaptionFrame) {
nsPoint captionOrigin;
nsRect prevCaptionRect;
mCaptionFrame->GetRect(prevCaptionRect);
if (priorInnerRect.width != innerMet.width) {
nsMargin ignorePadding;
// XXX only need to reflow if the caption is auto width
nsHTMLReflowMetrics captionMet(nsnull); // don't ask for MES, it hasn't changed
nscoord availWidth = GetCaptionAvailWidth(aPresContext, mCaptionFrame, aOuterRS,
&innerSize.width, &innerMarginNoAuto);
nsReflowStatus capStatus; // don't let the caption cause incomplete
rv = OuterReflowChild(aPresContext, mCaptionFrame, aOuterRS, captionMet, &availWidth,
captionSize, captionMargin, captionMarginNoAuto,
ignorePadding, eReflowReason_Resize, capStatus);
if (NS_FAILED(rv)) return rv;
GetCaptionOrigin(aPresContext, captionSide, containSize, innerSize,
innerMargin, captionSize, captionMargin, captionOrigin);
FinishReflowChild(mCaptionFrame, aPresContext, captionMet,
captionOrigin.x, captionOrigin.y, 0);
GetInnerOrigin(aPresContext, captionSide, containSize, captionSize,
captionMargin, innerSize, innerMargin, innerOrigin);
}
else {
// reposition the caption frame if necessary and set the inner's origin
captionSize = GetFrameSize(*mCaptionFrame);
nsMargin captionPadding;
GetMarginPadding(aPresContext, aOuterRS, mCaptionFrame, captionMargin,
captionMarginNoAuto, captionPadding);
GetCaptionOrigin(aPresContext, captionSide, containSize, innerSize,
innerMargin, captionSize, captionMargin, captionOrigin);
GetInnerOrigin(aPresContext, captionSide, containSize, captionSize,
captionMargin, innerSize, innerMargin, innerOrigin);
MoveFrameTo(aPresContext, mCaptionFrame, captionOrigin.x, captionOrigin.y);
}
if ((captionOrigin.x != prevCaptionRect.x) || (captionOrigin.x != prevCaptionRect.x)) {
captionMoved = PR_TRUE;
}
if ((captionOrigin.x != prevCaptionRect.x) || (captionOrigin.x != prevCaptionRect.x)) {
captionMoved = PR_TRUE;
}
}
else {
GetInnerOrigin(aPresContext, captionSide, containSize, captionSize,
captionMargin, innerSize, innerMargin, innerOrigin);
}
FinishReflowChild(mInnerTableFrame, aPresContext, innerMet,
innerOrigin.x, innerOrigin.y, 0);
UpdateReflowMetrics(captionSide, aOuterMet, innerMargin, innerMarginNoAuto,
innerPadding, captionMargin, captionMarginNoAuto);
nsSize desSize(aOuterMet.width, aOuterMet.height);
InvalidateDamage(aPresContext, captionSide, desSize, PR_FALSE, captionMoved);
return rv;
}
/* the only difference between an insert and a replace is a replace
checks the old maxElementSize and reflows the table only if it
has changed
*/
nsresult
nsTableOuterFrame::IR_CaptionInserted(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aOuterRS,
nsReflowStatus& aStatus)
{
PRUint8 captionSide = GetCaptionSide();
aStatus = NS_FRAME_COMPLETE;
// reflow the caption frame, getting it's MES
nsSize captionSize;
nsMargin captionMargin, captionMarginNoAuto, ignorePadding;
nsSize maxElementSize(0,0);
nsHTMLReflowMetrics captionMet(&maxElementSize);
// reflow the caption
nscoord availWidth = GetCaptionAvailWidth(aPresContext, mCaptionFrame, aOuterRS);
nsReflowStatus capStatus; // don't let the caption cause incomplete
nsresult rv = OuterReflowChild(aPresContext, mCaptionFrame, aOuterRS, captionMet,
&availWidth, captionSize, captionMargin, captionMarginNoAuto,
ignorePadding, eReflowReason_Initial, capStatus);
if (NS_FAILED(rv)) return rv;
mMinCaptionWidth = maxElementSize.width;
nsPoint captionOrigin(0,0);
nsMargin innerMargin, innerMarginNoAuto, innerPadding;
nsPoint innerOrigin;
nsSize containSize = GetContainingBlockSize(aOuterRS);
nsRect prevInnerRect;
mInnerTableFrame->GetRect(prevInnerRect);
// XXX: caption align = left|right ignored here!
// if the caption's MES + margins > outer width, reflow the inner table
if (mMinCaptionWidth + captionMarginNoAuto.left + captionMarginNoAuto.right > mRect.width) {
nsHTMLReflowMetrics innerMet(aDesiredSize.maxElementSize);
nsSize innerSize;
rv = OuterReflowChild(aPresContext, mInnerTableFrame, aOuterRS, innerMet,
nsnull, innerSize, innerMargin, innerMarginNoAuto,
innerPadding, eReflowReason_Resize, aStatus);
if (NS_FAILED(rv)) return rv;
GetInnerOrigin(aPresContext, captionSide, containSize, captionSize,
captionMargin, innerSize, innerMargin, innerOrigin);
rv = FinishReflowChild(mInnerTableFrame, aPresContext, innerMet,
innerOrigin.x, innerOrigin.y, 0);
if (NS_FAILED(rv)) return rv;
GetCaptionOrigin(aPresContext, captionSide, containSize, innerSize,
innerMargin, captionSize, captionMargin, captionOrigin);
}
else {
// reposition the inner frame if necessary and set the caption's origin
nsSize innerSize = GetFrameSize(*mInnerTableFrame);
GetMarginPadding(aPresContext, aOuterRS, mInnerTableFrame, innerMargin,
innerMarginNoAuto, innerPadding);
nsPoint innerOrigin;
GetInnerOrigin(aPresContext, captionSide, containSize, captionSize,
captionMargin, innerSize, innerMargin, innerOrigin);
GetCaptionOrigin(aPresContext, captionSide, containSize, innerSize,
innerMargin, captionSize, captionMargin, captionOrigin);
MoveFrameTo(aPresContext, mInnerTableFrame, innerOrigin.x, innerOrigin.y);
}
rv = FinishReflowChild(mCaptionFrame, aPresContext, captionMet,
captionOrigin.x, captionOrigin.y, 0);
UpdateReflowMetrics(captionSide, aDesiredSize, innerMargin, innerMarginNoAuto,
innerPadding, captionMargin, captionMarginNoAuto);
nsSize desSize(aDesiredSize.width, aDesiredSize.height);
PRBool innerMoved = (innerOrigin.x != prevInnerRect.x) || (innerOrigin.y != prevInnerRect.y);
InvalidateDamage(aPresContext, captionSide, desSize, innerMoved, PR_TRUE);
return rv;
}
PRBool nsTableOuterFrame::IR_CaptionChangedAxis(const nsStyleTable* aOldStyle,
const nsStyleTable* aNewStyle) const
{
PRBool result = PR_FALSE;
//XXX: write me to support left|right captions!
return result;
}
/**
* Reflow is a multi-step process.
* 1. First we reflow the caption frame and get its maximum element size. We
* do this once during our initial reflow and whenever the caption changes
* incrementally
* 2. Next we reflow the inner table. This gives us the actual table width.
* The table must be at least as wide as the caption maximum element size
* 3. Now that we have the table width we reflow the caption and gets its
* desired height
* 4. Then we place the caption and the inner table
*
* If the table height is constrained, e.g. page mode, then it's possible the
* inner table no longer fits and has to be reflowed again this time with s
* smaller available height
*/
NS_METHOD nsTableOuterFrame::Reflow(nsIPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aOuterRS,
nsReflowStatus& aStatus)
{
DO_GLOBAL_REFLOW_COUNT("nsTableOuterFrame", aOuterRS.reason);
#if defined DEBUG_TABLE_REFLOW | DEBUG_TABLE_REFLOW_TIMING
nsTableFrame::DebugReflow(this, (nsHTMLReflowState&)aOuterRS);
#endif
nsresult rv = NS_OK;
PRUint8 captionSide = GetCaptionSide();
// Initialize out parameters
aDesiredSize.width = aDesiredSize.height = 0;
if (nsnull != aDesiredSize.maxElementSize) {
aDesiredSize.maxElementSize->width = 0;
aDesiredSize.maxElementSize->height = 0;
}
aStatus = NS_FRAME_COMPLETE;
if (eReflowReason_Incremental == aOuterRS.reason) {
rv = IncrementalReflow(aPresContext, aDesiredSize, aOuterRS, aStatus);
}
else {
if (eReflowReason_Initial == aOuterRS.reason) {
// Set up our kids. They're already present, on an overflow list,
// or there are none so we'll create them now
MoveOverflowToChildList(aPresContext);
// Lay out the caption and get its maximum element size
if (nsnull != mCaptionFrame) {
nsSize maxElementSize;
nsHTMLReflowMetrics captionMet(&maxElementSize);
captionMet.maxElementSize = &maxElementSize;
nsHTMLReflowState captionReflowState(aPresContext, aOuterRS, mCaptionFrame,
nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
eReflowReason_Initial);
mCaptionFrame->WillReflow(aPresContext);
rv = mCaptionFrame->Reflow(aPresContext, captionMet, captionReflowState, aStatus);
mCaptionFrame->DidReflow(aPresContext, NS_FRAME_REFLOW_FINISHED);
mMinCaptionWidth = maxElementSize.width;
}
}
// At this point, we must have an inner table frame, and we might have a caption
NS_ASSERTION(mFrames.NotEmpty() && mInnerTableFrame, "incomplete children");
nsSize innerSize;
nsMargin innerMargin, innerMarginNoAuto, innerPadding;
// First reflow the inner table
nsHTMLReflowMetrics innerMet(aDesiredSize.maxElementSize);
rv = OuterReflowChild(aPresContext, mInnerTableFrame, aOuterRS, innerMet,
nsnull, innerSize, innerMargin, innerMarginNoAuto,
innerPadding, aOuterRS.reason, aStatus);
if (NS_FAILED(rv)) return rv;
if (NS_UNCONSTRAINEDSIZE == aOuterRS.availableWidth) {
// Remember the inner table's maximum width
mInnerTableMaximumWidth = innerMet.width;
}
nsPoint innerOrigin(0,0);
nsMargin captionMargin(0,0,0,0), captionMarginNoAuto(0,0,0,0), ignorePadding;
nsSize captionSize(0,0);
nsSize containSize = GetContainingBlockSize(aOuterRS);
// Now that we know the table width we can reflow the caption, and
// place the caption and the inner table
if (mCaptionFrame) {
// reflow the caption
nscoord availWidth = GetCaptionAvailWidth(aPresContext, mCaptionFrame, aOuterRS,
&innerSize.width, &innerMarginNoAuto);
nsHTMLReflowMetrics captionMet(nsnull);
nsReflowStatus capStatus; // don't let the caption cause incomplete
rv = OuterReflowChild(aPresContext, mCaptionFrame, aOuterRS, captionMet,
&availWidth, captionSize, captionMargin, captionMarginNoAuto,
ignorePadding, aOuterRS.reason, capStatus);
if (NS_FAILED(rv)) return rv;
nsPoint captionOrigin;
GetCaptionOrigin(aPresContext, captionSide, containSize, innerSize,
innerMargin, captionSize, captionMargin, captionOrigin);
FinishReflowChild(mCaptionFrame, aPresContext, captionMet,
captionOrigin.x, captionOrigin.y, 0);
GetInnerOrigin(aPresContext, captionSide, containSize, captionSize,
captionMargin, innerSize, innerMargin, innerOrigin);
// XXX If the height is constrained then we need to check whether the inner table still fits...
}
else {
GetInnerOrigin(aPresContext, captionSide, containSize, captionSize,
captionMargin, innerSize, innerMargin, innerOrigin);
}
FinishReflowChild(mInnerTableFrame, aPresContext, innerMet,
innerOrigin.x, innerOrigin.y, 0);
UpdateReflowMetrics(captionSide, aDesiredSize, innerMargin, innerMarginNoAuto,
innerPadding, captionMargin, captionMarginNoAuto);
}
// Return our desired rect
aDesiredSize.ascent = aDesiredSize.height;
aDesiredSize.descent = 0;
// See if we are supposed to compute our maximum width
if (aDesiredSize.mFlags & NS_REFLOW_CALC_MAX_WIDTH) {
// XXX this needs to consider the possibility of a caption being wider
// than the inner table, but this is the safest way to fix bug 55545
if (mInnerTableFrame) {
aDesiredSize.mMaximumWidth = ((nsTableFrame*)mInnerTableFrame)->GetPreferredWidth();
}
}
#if defined DEBUG_TABLE_REFLOW | DEBUG_TABLE_REFLOW_TIMING
nsTableFrame::DebugReflow(this, (nsHTMLReflowState&)aOuterRS, &aDesiredSize, aStatus);
#endif
return rv;
}
NS_METHOD nsTableOuterFrame::VerifyTree() const
{
return NS_OK;
}
/**
* Remove and delete aChild's next-in-flow(s). Updates the sibling and flow
* pointers.
*
* Updates the child count and content offsets of all containers that are
* affected
*
* Overloaded here because nsContainerFrame makes assumptions about pseudo-frames
* that are not true for tables.
*
* @param aChild child this child's next-in-flow
* @return PR_TRUE if successful and PR_FALSE otherwise
*/
void nsTableOuterFrame::DeleteChildsNextInFlow(nsIPresContext* aPresContext,
nsIFrame* aChild)
{
NS_PRECONDITION(mFrames.ContainsFrame(aChild), "bad geometric parent");
nsIFrame* nextInFlow;
aChild->GetNextInFlow(&nextInFlow);
NS_PRECONDITION(nsnull != nextInFlow, "null next-in-flow");
nsTableOuterFrame* parent;
nextInFlow->GetParent((nsIFrame**)&parent);
// If the next-in-flow has a next-in-flow then delete it too (and
// delete it first).
nsIFrame* nextNextInFlow;
nextInFlow->GetNextInFlow(&nextNextInFlow);
if (nsnull != nextNextInFlow) {
parent->DeleteChildsNextInFlow(aPresContext, nextInFlow);
}
// Disconnect the next-in-flow from the flow list
nsSplittableFrame::BreakFromPrevFlow(nextInFlow);
// Take the next-in-flow out of the parent's child list
if (parent->mFrames.FirstChild() == nextInFlow) {
nsIFrame* nextSibling;
nextInFlow->GetNextSibling(&nextSibling);
parent->mFrames.SetFrames(nextSibling);
} else {
nsIFrame* nextSibling;
// Because the next-in-flow is not the first child of the parent
// we know that it shares a parent with aChild. Therefore, we need
// to capture the next-in-flow's next sibling (in case the
// next-in-flow is the last next-in-flow for aChild AND the
// next-in-flow is not the last child in parent)
aChild->GetNextSibling(&nextSibling);
NS_ASSERTION(nextSibling == nextInFlow, "unexpected sibling");
nextInFlow->GetNextSibling(&nextSibling);
aChild->SetNextSibling(nextSibling);
}
// Delete the next-in-flow frame and adjust it's parent's child count
nextInFlow->Destroy(aPresContext);
#ifdef NS_DEBUG
aChild->GetNextInFlow(&nextInFlow);
NS_POSTCONDITION(nsnull == nextInFlow, "non null next-in-flow");
#endif
}
NS_IMETHODIMP
nsTableOuterFrame::GetFrameType(nsIAtom** aType) const
{
NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer");
*aType = nsLayoutAtoms::tableOuterFrame;
NS_ADDREF(*aType);
return NS_OK;
}
/* ----- global methods ----- */
/*------------------ nsITableLayout methods ------------------------------*/
NS_IMETHODIMP
nsTableOuterFrame::GetCellDataAt(PRInt32 aRowIndex, PRInt32 aColIndex,
nsIDOMElement* &aCell, //out params
PRInt32& aStartRowIndex, PRInt32& aStartColIndex,
PRInt32& aRowSpan, PRInt32& aColSpan,
PRInt32& aActualRowSpan, PRInt32& aActualColSpan,
PRBool& aIsSelected)
{
if (!mInnerTableFrame) { return NS_ERROR_NOT_INITIALIZED; }
nsITableLayout *inner;
nsresult result = mInnerTableFrame->QueryInterface(NS_GET_IID(nsITableLayout), (void **)&inner);
if (NS_SUCCEEDED(result) && inner)
{
return (inner->GetCellDataAt(aRowIndex, aColIndex, aCell,
aStartRowIndex, aStartColIndex,
aRowSpan, aColSpan, aActualRowSpan, aActualColSpan,
aIsSelected));
}
return NS_ERROR_NULL_POINTER;
}
NS_IMETHODIMP nsTableOuterFrame::GetTableSize(PRInt32& aRowCount, PRInt32& aColCount)
{
if (!mInnerTableFrame) { return NS_ERROR_NOT_INITIALIZED; }
nsITableLayout *inner;
nsresult result = mInnerTableFrame->QueryInterface(NS_GET_IID(nsITableLayout), (void **)&inner);
if (NS_SUCCEEDED(result) && inner)
{
return (inner->GetTableSize(aRowCount, aColCount));
}
return NS_ERROR_NULL_POINTER;
}
/*---------------- end of nsITableLayout implementation ------------------*/
nsresult
NS_NewTableOuterFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
if (nsnull == aNewFrame) {
return NS_ERROR_NULL_POINTER;
}
nsTableOuterFrame* it = new (aPresShell) nsTableOuterFrame;
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
*aNewFrame = it;
return NS_OK;
}
#ifdef DEBUG
NS_IMETHODIMP
nsTableOuterFrame::GetFrameName(nsString& aResult) const
{
return MakeFrameName("TableOuter", aResult);
}
NS_IMETHODIMP
nsTableOuterFrame::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const
{
if (!aResult) {
return NS_ERROR_NULL_POINTER;
}
PRUint32 sum = sizeof(*this);
*aResult = sum;
return NS_OK;
}
#endif