pjs/layout/generic/nsAreaFrame.cpp

551 строка
17 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 "nsAreaFrame.h"
#include "nsBlockBandData.h"
#include "nsIReflowCommand.h"
#include "nsIStyleContext.h"
#include "nsStyleConsts.h"
#include "nsIPresContext.h"
#include "nsIViewManager.h"
#include "nsSpaceManager.h"
#include "nsHTMLAtoms.h"
#include "nsIView.h"
#include "nsHTMLIIDs.h"
#include "nsHTMLValue.h"
#include "nsHTMLParts.h"
#include "nsLayoutAtoms.h"
#undef NOISY_MAX_ELEMENT_SIZE
#undef NOISY_SPACEMANAGER
#undef NOISY_FINAL_SIZE
nsresult
NS_NewAreaFrame(nsIFrame** aNewFrame, PRUint32 aFlags)
{
NS_PRECONDITION(aNewFrame, "null OUT ptr");
if (nsnull == aNewFrame) {
return NS_ERROR_NULL_POINTER;
}
nsAreaFrame* it = new nsAreaFrame;
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
it->SetFlags(aFlags);
*aNewFrame = it;
return NS_OK;
}
nsAreaFrame::nsAreaFrame()
{
}
nsAreaFrame::~nsAreaFrame()
{
NS_IF_RELEASE(mSpaceManager);
}
/////////////////////////////////////////////////////////////////////////////
// nsISupports
NS_IMETHODIMP
nsAreaFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if (aIID.Equals(kIAreaFrameIID)) {
nsIAreaFrame* tmp = (nsIAreaFrame*)this;
*aInstancePtr = (void*)tmp;
return NS_OK;
}
return nsBlockFrame::QueryInterface(aIID, aInstancePtr);
}
/////////////////////////////////////////////////////////////////////////////
// nsIFrame
NS_IMETHODIMP
nsAreaFrame::Init(nsIPresContext& aPresContext,
nsIContent* aContent,
nsIFrame* aParent,
nsIStyleContext* aContext,
nsIFrame* aPrevInFlow)
{
nsresult rv;
// Let the block frame do its initialization
rv = nsBlockFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow);
// Create a space manager if requested
if (0 == (mFlags & NS_AREA_NO_SPACE_MGR)) {
mSpaceManager = new nsSpaceManager(this);
if (!mSpaceManager) {
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(mSpaceManager);
}
return rv;
}
NS_IMETHODIMP
nsAreaFrame::Destroy(nsIPresContext& aPresContext)
{
mAbsoluteContainer.DestroyFrames(this, aPresContext);
return nsBlockFrame::Destroy(aPresContext);
}
NS_IMETHODIMP
nsAreaFrame::SetInitialChildList(nsIPresContext& aPresContext,
nsIAtom* aListName,
nsIFrame* aChildList)
{
nsresult rv;
if (nsLayoutAtoms::absoluteList == aListName) {
rv = mAbsoluteContainer.SetInitialChildList(this, aPresContext, aListName, aChildList);
} else {
rv = nsBlockFrame::SetInitialChildList(aPresContext, aListName, aChildList);
}
return rv;
}
NS_IMETHODIMP
nsAreaFrame::AppendFrames(nsIPresContext& aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aFrameList)
{
nsresult rv;
if (nsLayoutAtoms::absoluteList == aListName) {
rv = mAbsoluteContainer.AppendFrames(this, aPresContext, aPresShell, aListName,
aFrameList);
} else {
rv = nsBlockFrame::AppendFrames(aPresContext, aPresShell, aListName,
aFrameList);
}
return rv;
}
NS_IMETHODIMP
nsAreaFrame::InsertFrames(nsIPresContext& aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aPrevFrame,
nsIFrame* aFrameList)
{
nsresult rv;
if (nsLayoutAtoms::absoluteList == aListName) {
rv = mAbsoluteContainer.InsertFrames(this, aPresContext, aPresShell, aListName,
aPrevFrame, aFrameList);
} else {
rv = nsBlockFrame::InsertFrames(aPresContext, aPresShell, aListName,
aPrevFrame, aFrameList);
}
return rv;
}
NS_IMETHODIMP
nsAreaFrame::RemoveFrame(nsIPresContext& aPresContext,
nsIPresShell& aPresShell,
nsIAtom* aListName,
nsIFrame* aOldFrame)
{
nsresult rv;
if (nsLayoutAtoms::absoluteList == aListName) {
rv = mAbsoluteContainer.RemoveFrame(this, aPresContext, aPresShell, aListName, aOldFrame);
} else {
rv = nsBlockFrame::RemoveFrame(aPresContext, aPresShell, aListName, aOldFrame);
}
return rv;
}
NS_IMETHODIMP
nsAreaFrame::GetAdditionalChildListName(PRInt32 aIndex,
nsIAtom** aListName) const
{
NS_PRECONDITION(nsnull != aListName, "null OUT parameter pointer");
if (aIndex <= NS_BLOCK_FRAME_LAST_LIST_INDEX) {
return nsBlockFrame::GetAdditionalChildListName(aIndex, aListName);
}
*aListName = nsnull;
if (NS_AREA_FRAME_ABSOLUTE_LIST_INDEX == aIndex) {
*aListName = nsLayoutAtoms::absoluteList;
NS_ADDREF(*aListName);
}
return NS_OK;
}
NS_IMETHODIMP
nsAreaFrame::FirstChild(nsIAtom* aListName, nsIFrame** aFirstChild) const
{
NS_PRECONDITION(nsnull != aFirstChild, "null OUT parameter pointer");
if (aListName == nsLayoutAtoms::absoluteList) {
return mAbsoluteContainer.FirstChild(this, aListName, aFirstChild);
}
return nsBlockFrame::FirstChild(aListName, aFirstChild);
}
#ifdef DEBUG
NS_IMETHODIMP
nsAreaFrame::Paint(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsFramePaintLayer aWhichLayer)
{
// Note: all absolutely positioned elements have views so we don't
// need to worry about painting them
nsresult rv = nsBlockFrame::Paint(aPresContext, aRenderingContext,
aDirtyRect, aWhichLayer);
if ((NS_FRAME_PAINT_LAYER_DEBUG == aWhichLayer) && GetShowFrameBorders()) {
// Render the bands in the spacemanager
nsISpaceManager* sm = mSpaceManager;
if (nsnull != sm) {
nsBlockBandData band;
band.Init(sm, nsSize(mRect.width, mRect.height));
nscoord y = 0;
while (y < mRect.height) {
nsRect availArea;
band.GetAvailableSpace(y, availArea);
// Render a box and a diagonal line through the band
aRenderingContext.SetColor(NS_RGB(0,255,0));
aRenderingContext.DrawRect(0, availArea.y,
mRect.width, availArea.height);
aRenderingContext.DrawLine(0, availArea.y,
mRect.width, availArea.YMost());
// Render boxes and opposite diagonal lines around the
// unavailable parts of the band.
PRInt32 i;
for (i = 0; i < band.GetTrapezoidCount(); i++) {
const nsBandTrapezoid* trapezoid = band.GetTrapezoid(i);
if (nsBandTrapezoid::Available != trapezoid->mState) {
nsRect r;
trapezoid->GetRect(r);
if (nsBandTrapezoid::OccupiedMultiple == trapezoid->mState) {
aRenderingContext.SetColor(NS_RGB(0,255,128));
}
else {
aRenderingContext.SetColor(NS_RGB(128,255,0));
}
aRenderingContext.DrawRect(r);
aRenderingContext.DrawLine(r.x, r.YMost(), r.XMost(), r.y);
}
}
y = availArea.YMost();
}
}
}
return rv;
}
#endif
// Return the x-most and y-most for the child absolutely positioned
// elements
NS_IMETHODIMP
nsAreaFrame::GetPositionedInfo(nscoord& aXMost, nscoord& aYMost) const
{
nsresult rv = mAbsoluteContainer.GetPositionedInfo(this, aXMost, aYMost);
// If we have child frames that stick outside of our box, and they should
// be visible, then include them too so the total size is correct
if (mState & NS_FRAME_OUTSIDE_CHILDREN) {
const nsStyleDisplay* display = (const nsStyleDisplay*)
mStyleContext->GetStyleData(eStyleStruct_Display);
if (NS_STYLE_OVERFLOW_VISIBLE == display->mOverflow) {
if (mCombinedArea.XMost() > aXMost) {
aXMost = mCombinedArea.XMost();
}
if (mCombinedArea.YMost() > aYMost) {
aYMost = mCombinedArea.YMost();
}
}
}
return rv;
}
NS_IMETHODIMP
nsAreaFrame::GetSpaceManager(nsISpaceManager** aSpaceManagerResult)
{
if (!aSpaceManagerResult) {
NS_WARNING("null ptr");
return NS_ERROR_NULL_POINTER;
}
*aSpaceManagerResult = mSpaceManager;
NS_IF_ADDREF(mSpaceManager);
return NS_OK;
}
static void
CalculateContainingBlock(const nsHTMLReflowState& aReflowState,
nscoord aFrameWidth,
nscoord aFrameHeight,
nscoord& aContainingBlockWidth,
nscoord& aContainingBlockHeight)
{
aContainingBlockWidth = -1; // have reflow state calculate
aContainingBlockHeight = -1; // have reflow state calculate
// We should probably do this for all frames, but for the time being just
// do this for relatively positioned block frames. The issue there is that
// for a 'height' of 'auto' the reflow state code won't know how to calculate
// the containing block height
if (NS_STYLE_POSITION_RELATIVE == aReflowState.mStylePosition->mPosition) {
aContainingBlockWidth = aFrameWidth;
aContainingBlockHeight = aFrameHeight;
// Containing block is relative to the padding edge
nsMargin border;
if (!aReflowState.mStyleSpacing->GetBorder(border)) {
NS_NOTYETIMPLEMENTED("percentage border");
}
aContainingBlockWidth -= border.left + border.right;
aContainingBlockHeight -= border.top + border.bottom;
}
}
NS_IMETHODIMP
nsAreaFrame::Reflow(nsIPresContext& aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
("enter nsAreaFrame::Reflow: maxSize=%d,%d reason=%d",
aReflowState.availableWidth,
aReflowState.availableHeight,
aReflowState.reason));
nsresult rv = NS_OK;
// See if it's an incremental reflow command
if (eReflowReason_Incremental == aReflowState.reason) {
// Give the absolute positioning code a chance to handle it
nscoord containingBlockWidth;
nscoord containingBlockHeight;
PRBool handled;
CalculateContainingBlock(aReflowState, mRect.width, mRect.height,
containingBlockWidth, containingBlockHeight);
mAbsoluteContainer.IncrementalReflow(this, aPresContext, aReflowState,
containingBlockWidth, containingBlockHeight,
handled);
// If the incremental reflow command was handled by the absolute positioning
// code, then we're all done
if (handled) {
// Just return our current size as our desired size
aDesiredSize.width = mRect.width;
aDesiredSize.height = mRect.height;
aDesiredSize.ascent = mRect.height;
aDesiredSize.descent = 0;
// Whether or not we're complete hasn't changed
aStatus = (nsnull != mNextInFlow) ? NS_FRAME_NOT_COMPLETE : NS_FRAME_COMPLETE;
return rv;
}
}
// If we have a space manager, then set it in the reflow state
if (nsnull != mSpaceManager) {
// Modify the existing reflow state
nsHTMLReflowState& reflowState = (nsHTMLReflowState&)aReflowState;
reflowState.mSpaceManager = mSpaceManager;
// Clear the spacemanager's regions
mSpaceManager->ClearRegions();
}
#ifdef NOISY_SPACEMANAGER
if (eReflowReason_Incremental == aReflowState.reason) {
if (mSpaceManager) {
ListTag(stdout);
printf(": space-manager before reflow\n");
mSpaceManager->List(stdout);
}
}
#endif
// Let the block frame do its reflow first
rv = nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus);
#ifdef NOISY_SPACEMANAGER
if (eReflowReason_Incremental == aReflowState.reason) {
if (mSpaceManager) {
ListTag(stdout);
printf(": space-manager after reflow\n");
mSpaceManager->List(stdout);
}
}
#endif
if (mFlags & NS_AREA_WRAP_SIZE) {
// When the area frame is supposed to wrap around all in-flow
// children, make sure its big enough to include those that stick
// outside the box.
if (NS_FRAME_OUTSIDE_CHILDREN & mState) {
nscoord xMost = aDesiredSize.mCombinedArea.XMost();
if (xMost > aDesiredSize.width) {
#ifdef NOISY_FINAL_SIZE
ListTag(stdout);
printf(": changing desired width from %d to %d\n",
aDesiredSize.width, xMost);
#endif
aDesiredSize.width = xMost;
}
nscoord yMost = aDesiredSize.mCombinedArea.YMost();
if (yMost > aDesiredSize.height) {
#ifdef NOISY_FINAL_SIZE
ListTag(stdout);
printf(": changing desired height from %d to %d\n",
aDesiredSize.height, yMost);
#endif
aDesiredSize.height = yMost;
}
}
}
// Let the absolutely positioned container reflow any absolutely positioned
// child frames that need to be reflowed, e.g., elements with a percentage
// based width/height
if (NS_SUCCEEDED(rv)) {
nscoord containingBlockWidth;
nscoord containingBlockHeight;
CalculateContainingBlock(aReflowState, aDesiredSize.width, aDesiredSize.height,
containingBlockWidth, containingBlockHeight);
rv = mAbsoluteContainer.Reflow(this, aPresContext, aReflowState,
containingBlockWidth, containingBlockHeight);
}
#ifdef NOISY_MAX_ELEMENT_SIZE
ListTag(stdout);
printf(": maxElementSize=%d,%d desiredSize=%d,%d\n",
aDesiredSize.maxElementSize ? aDesiredSize.maxElementSize->width : 0,
aDesiredSize.maxElementSize ? aDesiredSize.maxElementSize->height : 0,
aDesiredSize.width, aDesiredSize.height);
#endif
// If we have children that stick outside our box, then remember the
// combined area, because we'll need it later when sizing our view
if (mState & NS_FRAME_OUTSIDE_CHILDREN) {
mCombinedArea = aDesiredSize.mCombinedArea;
}
return rv;
}
NS_IMETHODIMP
nsAreaFrame::GetFrameType(nsIAtom** aType) const
{
NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer");
*aType = nsLayoutAtoms::areaFrame;
NS_ADDREF(*aType);
return NS_OK;
}
NS_IMETHODIMP
nsAreaFrame::DidReflow(nsIPresContext& aPresContext,
nsDidReflowStatus aStatus)
{
if (NS_FRAME_REFLOW_FINISHED == aStatus) {
// If we should position our view, and we have child frames that stick
// outside our box, then we need to size our view large enough to include
// those child frames
if ((mState & NS_FRAME_SYNC_FRAME_AND_VIEW) &&
(mState & NS_FRAME_OUTSIDE_CHILDREN)) {
nsIView* view;
const nsStyleDisplay* display = (const nsStyleDisplay*)
mStyleContext->GetStyleData(eStyleStruct_Display);
GetView(&view);
if (view && (NS_STYLE_OVERFLOW_VISIBLE == display->mOverflow)) {
// Don't let our base class position the view since we're doing it
mState &= ~NS_FRAME_SYNC_FRAME_AND_VIEW;
// Set the view's bit that indicates that it has transparent content
nsIViewManager* vm;
view->GetViewManager(vm);
vm->SetViewContentTransparency(view, PR_TRUE);
// Position and size view relative to its parent, not relative to our
// parent frame (our parent frame may not have a view).
nsIView* parentWithView;
nsPoint origin;
// XXX We need to handle the case where child frames stick out on the
// left and top edges as well...
GetOffsetFromView(origin, &parentWithView);
vm->ResizeView(view, mCombinedArea.XMost(), mCombinedArea.YMost());
vm->MoveViewTo(view, origin.x, origin.y);
NS_RELEASE(vm);
// Call our base class
nsresult rv = nsBlockFrame::DidReflow(aPresContext, aStatus);
// Set the flag again...
mState |= NS_FRAME_SYNC_FRAME_AND_VIEW;
return rv;
}
}
}
return nsBlockFrame::DidReflow(aPresContext, aStatus);
}
/////////////////////////////////////////////////////////////////////////////
// Diagnostics
NS_IMETHODIMP
nsAreaFrame::GetFrameName(nsString& aResult) const
{
return MakeFrameName("Area", aResult);
}
#ifdef DEBUG
NS_IMETHODIMP
nsAreaFrame::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const
{
if (!aResult) {
return NS_ERROR_NULL_POINTER;
}
nsBlockFrame::SizeOf(aHandler, aResult);
*aResult += sizeof(*this) - sizeof(nsBlockFrame);
return NS_OK;
}
#endif