/* -*- 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