395 lines
13 KiB
C++
395 lines
13 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 "nsBodyFrame.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIContentDelegate.h"
|
|
#include "nsBlockFrame.h"
|
|
#include "nsReflowCommand.h"
|
|
#include "nsIStyleContext.h"
|
|
#include "nsStyleConsts.h"
|
|
#include "nsIPresContext.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsIViewManager.h"
|
|
#include "nsIDeviceContext.h"
|
|
#include "nsColumnFrame.h"
|
|
#include "nsSpaceManager.h"
|
|
|
|
static NS_DEFINE_IID(kIRunaroundIID, NS_IRUNAROUND_IID);
|
|
static NS_DEFINE_IID(kIAnchoredItemsIID, NS_IANCHOREDITEMS_IID);
|
|
|
|
static NS_DEFINE_IID(kStyleSpacingSID, NS_STYLESPACING_SID);
|
|
|
|
nsresult nsBodyFrame::NewFrame(nsIFrame** aInstancePtrResult,
|
|
nsIContent* aContent,
|
|
PRInt32 aIndexInParent,
|
|
nsIFrame* aParent)
|
|
{
|
|
NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr");
|
|
if (nsnull == aInstancePtrResult) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
nsIFrame* it = new nsBodyFrame(aContent, aIndexInParent, aParent);
|
|
if (nsnull == it) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
*aInstancePtrResult = it;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsBodyFrame::nsBodyFrame(nsIContent* aContent,
|
|
PRInt32 aIndexInParent,
|
|
nsIFrame* aParentFrame)
|
|
: nsHTMLContainerFrame(aContent, aIndexInParent, aParentFrame)
|
|
{
|
|
mSpaceManager = new SpaceManager(this);
|
|
NS_ADDREF(mSpaceManager);
|
|
}
|
|
|
|
nsBodyFrame::~nsBodyFrame()
|
|
{
|
|
NS_RELEASE(mSpaceManager);
|
|
}
|
|
|
|
nsresult
|
|
nsBodyFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
|
|
{
|
|
NS_PRECONDITION(0 != aInstancePtr, "null ptr");
|
|
if (NULL == aInstancePtr) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
if (aIID.Equals(kIAnchoredItemsIID)) {
|
|
*aInstancePtr = (void*) ((nsIAnchoredItems*) this);
|
|
return NS_OK;
|
|
}
|
|
return nsHTMLContainerFrame::QueryInterface(aIID, aInstancePtr);
|
|
}
|
|
|
|
void nsBodyFrame::CreateColumnFrame(nsIPresContext* aPresContext)
|
|
{
|
|
// Do we have a prev-in-flow?
|
|
if (nsnull == mPrevInFlow) {
|
|
// No, create a column pseudo frame
|
|
mFirstChild = new ColumnFrame(mContent, mIndexInParent, this);
|
|
mChildCount = 1;
|
|
|
|
// Resolve style and set the style context
|
|
nsIStyleContext* styleContext =
|
|
aPresContext->ResolveStyleContextFor(mContent, this);
|
|
mFirstChild->SetStyleContext(styleContext);
|
|
NS_RELEASE(styleContext);
|
|
} else {
|
|
nsBodyFrame* prevBody = (nsBodyFrame*)mPrevInFlow;
|
|
|
|
nsIFrame* prevColumn = prevBody->mFirstChild;
|
|
NS_ASSERTION(prevBody->ChildIsPseudoFrame(prevColumn), "bad previous column");
|
|
|
|
// Create a continuing column
|
|
prevColumn->CreateContinuingFrame(aPresContext, this, mFirstChild);
|
|
mChildCount = 1;
|
|
}
|
|
}
|
|
|
|
nsSize nsBodyFrame::GetColumnAvailSpace(nsIPresContext* aPresContext,
|
|
nsStyleSpacing* aSpacing,
|
|
const nsSize& aMaxSize)
|
|
{
|
|
nsSize result(aMaxSize);
|
|
|
|
// If we're not being used as a pseudo frame then make adjustments
|
|
// for border/padding and a vertical scrollbar
|
|
if (!IsPseudoFrame()) {
|
|
// If our width is constrained then subtract for the border/padding
|
|
if (aMaxSize.width != NS_UNCONSTRAINEDSIZE) {
|
|
result.width -= aSpacing->mBorderPadding.left +
|
|
aSpacing->mBorderPadding.right;
|
|
if (! aPresContext->IsPaginated()) {
|
|
nsIDeviceContext* dc = aPresContext->GetDeviceContext();
|
|
result.width -= NS_TO_INT_ROUND(dc->GetScrollBarWidth());
|
|
NS_RELEASE(dc);
|
|
}
|
|
}
|
|
// If our height is constrained then subtract for the border/padding
|
|
if (aMaxSize.height != NS_UNCONSTRAINEDSIZE) {
|
|
result.height -= aSpacing->mBorderPadding.top +
|
|
aSpacing->mBorderPadding.bottom;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_METHOD nsBodyFrame::ResizeReflow(nsIPresContext* aPresContext,
|
|
nsReflowMetrics& aDesiredSize,
|
|
const nsSize& aMaxSize,
|
|
nsSize* aMaxElementSize,
|
|
ReflowStatus& aStatus)
|
|
{
|
|
aStatus = frComplete; // initialize out parameter
|
|
|
|
// Do we have any children?
|
|
if (nsnull == mFirstChild) {
|
|
// No, create a column frame
|
|
CreateColumnFrame(aPresContext);
|
|
}
|
|
|
|
// Reflow the column
|
|
if (nsnull != mFirstChild) {
|
|
// Clear any regions that are marked as unavailable
|
|
mSpaceManager->ClearRegions();
|
|
|
|
// Get our border/padding info
|
|
nsStyleSpacing* mySpacing =
|
|
(nsStyleSpacing*)mStyleContext->GetData(kStyleSpacingSID);
|
|
|
|
// Compute the column's max size
|
|
nsSize columnMaxSize = GetColumnAvailSpace(aPresContext, mySpacing,
|
|
aMaxSize);
|
|
|
|
// XXX Style code should be dealing with this...
|
|
PRBool isPseudoFrame = IsPseudoFrame();
|
|
nscoord leftInset = 0, topInset = 0;
|
|
if (!isPseudoFrame) {
|
|
leftInset = mySpacing->mBorderPadding.left;
|
|
topInset = mySpacing->mBorderPadding.top;
|
|
}
|
|
|
|
// Get the column's desired rect
|
|
nsRect desiredRect;
|
|
nsIRunaround* reflowRunaround;
|
|
|
|
mSpaceManager->Translate(leftInset, topInset);
|
|
mFirstChild->QueryInterface(kIRunaroundIID, (void**)&reflowRunaround);
|
|
reflowRunaround->ResizeReflow(aPresContext, mSpaceManager, columnMaxSize,
|
|
desiredRect, aMaxElementSize, aStatus);
|
|
mSpaceManager->Translate(-leftInset, -topInset);
|
|
|
|
// Place and size the column
|
|
desiredRect.x += leftInset;
|
|
desiredRect.y += topInset;
|
|
mFirstChild->SetRect(desiredRect);
|
|
|
|
// Set our last content offset and whether the last content is complete
|
|
// based on the state of the pseudo frame
|
|
SetLastContentOffset(mFirstChild);
|
|
|
|
// Return our desired size. Take into account the y-most floater
|
|
aDesiredSize.width = desiredRect.XMost();
|
|
aDesiredSize.height = PR_MAX(desiredRect.YMost(), mSpaceManager->YMost());
|
|
|
|
if (!isPseudoFrame) {
|
|
aDesiredSize.width += mySpacing->mBorderPadding.left + mySpacing->mBorderPadding.right;
|
|
aDesiredSize.height += mySpacing->mBorderPadding.top + mySpacing->mBorderPadding.bottom;
|
|
}
|
|
aDesiredSize.ascent = aDesiredSize.height;
|
|
aDesiredSize.descent = 0;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_METHOD nsBodyFrame::VerifyTree() const
|
|
{
|
|
#ifdef NS_DEBUG
|
|
// Check our child count
|
|
PRInt32 len = LengthOf(mFirstChild);
|
|
NS_ASSERTION(len == mChildCount, "bad child count");
|
|
nsIFrame* lastChild;
|
|
|
|
LastChild(lastChild);
|
|
if (len != 0) {
|
|
NS_ASSERTION(nsnull != lastChild, "bad last child");
|
|
}
|
|
NS_ASSERTION(nsnull == mOverflowList, "bad overflow list");
|
|
|
|
// Make sure our content offsets are sane
|
|
NS_ASSERTION(mFirstContentOffset <= mLastContentOffset, "bad offsets");
|
|
|
|
// Verify child content offsets
|
|
nsIFrame* child = mFirstChild;
|
|
while (nsnull != child) {
|
|
// Make sure that the child's tree is valid
|
|
child->VerifyTree();
|
|
child->GetNextSibling(child);
|
|
}
|
|
|
|
// Make sure that our flow blocks offsets are all correct
|
|
if (nsnull == mPrevInFlow) {
|
|
PRInt32 nextOffset = NextChildOffset();
|
|
nsBodyFrame* nextInFlow = (nsBodyFrame*) mNextInFlow;
|
|
while (nsnull != nextInFlow) {
|
|
NS_ASSERTION(0 != nextInFlow->mChildCount, "empty next-in-flow");
|
|
NS_ASSERTION(nextInFlow->GetFirstContentOffset() == nextOffset,
|
|
"bad next-in-flow first offset");
|
|
nextOffset = nextInFlow->NextChildOffset();
|
|
nextInFlow = (nsBodyFrame*) nextInFlow->mNextInFlow;
|
|
}
|
|
}
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_METHOD nsBodyFrame::IncrementalReflow(nsIPresContext* aPresContext,
|
|
nsReflowMetrics& aDesiredSize,
|
|
const nsSize& aMaxSize,
|
|
nsReflowCommand& aReflowCommand,
|
|
ReflowStatus& aStatus)
|
|
{
|
|
// Get our border/padding info
|
|
nsStyleSpacing* mySpacing =
|
|
(nsStyleSpacing*)mStyleContext->GetData(kStyleSpacingSID);
|
|
|
|
// XXX Style code should be dealing with this...
|
|
PRBool isPseudoFrame = IsPseudoFrame();
|
|
nscoord leftInset = 0, topInset = 0;
|
|
if (!isPseudoFrame) {
|
|
leftInset = mySpacing->mBorderPadding.left;
|
|
topInset = mySpacing->mBorderPadding.top;
|
|
}
|
|
|
|
// XXX Clear the list of regions. This fixes a problem with the way reflow
|
|
// appended is currently working (we're reflowing some framems twice)
|
|
mSpaceManager->ClearRegions();
|
|
mSpaceManager->Translate(leftInset, topInset);
|
|
|
|
// Is the reflow command targeted for us?
|
|
if (aReflowCommand.GetTarget() == this) {
|
|
// Currently we only support appended content
|
|
if (aReflowCommand.GetType() != nsReflowCommand::FrameAppended) {
|
|
NS_NOTYETIMPLEMENTED("unexpected reflow command");
|
|
}
|
|
|
|
// Compute the column's max size
|
|
nsSize columnMaxSize = GetColumnAvailSpace(aPresContext, mySpacing,
|
|
aMaxSize);
|
|
|
|
// Pass the command along to our column pseudo frame
|
|
nsIRunaround* reflowRunaround;
|
|
nsRect aDesiredRect;
|
|
|
|
NS_ASSERTION(nsnull != mFirstChild, "no first child");
|
|
mFirstChild->QueryInterface(kIRunaroundIID, (void**)&reflowRunaround);
|
|
reflowRunaround->IncrementalReflow(aPresContext, mSpaceManager,
|
|
columnMaxSize, aDesiredRect, aReflowCommand, aStatus);
|
|
|
|
// Place and size the column
|
|
aDesiredRect.x += leftInset;
|
|
aDesiredRect.y += topInset;
|
|
mFirstChild->SetRect(aDesiredRect);
|
|
|
|
// Set our last content offset and whether the last content is complete
|
|
// based on the state of the pseudo frame
|
|
SetLastContentOffset(mFirstChild);
|
|
|
|
// Return our desired size
|
|
aDesiredSize.width = aDesiredRect.XMost();
|
|
aDesiredSize.height = aDesiredRect.YMost();
|
|
if (!isPseudoFrame) {
|
|
aDesiredSize.width += mySpacing->mBorderPadding.left + mySpacing->mBorderPadding.right;
|
|
aDesiredSize.height += mySpacing->mBorderPadding.top + mySpacing->mBorderPadding.bottom;
|
|
}
|
|
aDesiredSize.ascent = aDesiredSize.height;
|
|
aDesiredSize.descent = 0;
|
|
} else {
|
|
NS_NOTYETIMPLEMENTED("unexpected reflow command");
|
|
nsRect desiredRect;
|
|
nsIFrame* child;
|
|
|
|
aStatus = aReflowCommand.Next(mSpaceManager, desiredRect, aMaxSize, child);
|
|
|
|
// XXX Deal with next in flow, adjusting of siblings, adjusting the
|
|
// content length...
|
|
|
|
// Return our desired size
|
|
aDesiredSize.width = 0;
|
|
aDesiredSize.height = 0;
|
|
aDesiredSize.ascent = aDesiredSize.height;
|
|
aDesiredSize.descent = 0;
|
|
}
|
|
|
|
mSpaceManager->Translate(-leftInset, -topInset);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_METHOD nsBodyFrame::ContentAppended(nsIPresShell* aShell,
|
|
nsIPresContext* aPresContext,
|
|
nsIContent* aContainer)
|
|
{
|
|
NS_ASSERTION(mContent == aContainer, "bad content-appended target");
|
|
|
|
// Get the last-in-flow
|
|
nsBodyFrame* flow = (nsBodyFrame*)GetLastInFlow();
|
|
|
|
// Since body frame's have only a single pseudo-frame in them,
|
|
// pass on the content-appended call to the pseudo-frame
|
|
PRInt32 oldLastContentOffset = mLastContentOffset;
|
|
flow->mFirstChild->ContentAppended(aShell, aPresContext, aContainer);
|
|
|
|
// Now generate a frame reflow command aimed at flow
|
|
nsReflowCommand* rc =
|
|
new nsReflowCommand(aPresContext, flow, nsReflowCommand::FrameAppended,
|
|
oldLastContentOffset);
|
|
aShell->AppendReflowCommand(rc);
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsBodyFrame::AddAnchoredItem(nsIFrame* aAnchoredItem,
|
|
AnchoringPosition aPosition,
|
|
nsIFrame* aContainer)
|
|
{
|
|
aAnchoredItem->SetGeometricParent(this);
|
|
// Add the item to the end of the child list
|
|
nsIFrame* lastChild;
|
|
|
|
LastChild(lastChild);
|
|
lastChild->SetNextSibling(aAnchoredItem);
|
|
aAnchoredItem->SetNextSibling(nsnull);
|
|
mChildCount++;
|
|
}
|
|
|
|
void nsBodyFrame::RemoveAnchoredItem(nsIFrame* aAnchoredItem)
|
|
{
|
|
NS_PRECONDITION(IsChild(aAnchoredItem), "bad anchored item");
|
|
NS_ASSERTION(aAnchoredItem != mFirstChild, "unexpected anchored item");
|
|
// Remove the anchored item from the child list
|
|
// XXX Implement me
|
|
mChildCount--;
|
|
}
|
|
|
|
NS_METHOD nsBodyFrame::CreateContinuingFrame(nsIPresContext* aPresContext,
|
|
nsIFrame* aParent,
|
|
nsIFrame*& aContinuingFrame)
|
|
{
|
|
nsBodyFrame* cf = new nsBodyFrame(mContent, mIndexInParent, aParent);
|
|
PrepareContinuingFrame(aPresContext, aParent, cf);
|
|
aContinuingFrame = cf;
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX use same logic as block frame?
|
|
PRIntn nsBodyFrame::GetSkipSides() const
|
|
{
|
|
PRIntn skip = 0;
|
|
if (nsnull != mPrevInFlow) {
|
|
skip |= 1 << NS_SIDE_TOP;
|
|
}
|
|
if (nsnull != mNextInFlow) {
|
|
skip |= 1 << NS_SIDE_BOTTOM;
|
|
}
|
|
return skip;
|
|
}
|