/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * 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 Communicator client code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // // Eric Vaughan // Netscape Communications // // See documentation in associated header file // #include "nsBoxLayoutState.h" #include "nsBoxFrame.h" #include "nsStyleContext.h" #include "nsIPresContext.h" #include "nsCOMPtr.h" #include "nsUnitConversion.h" #include "nsHTMLAtoms.h" #include "nsXULAtoms.h" #include "nsHTMLReflowCommand.h" #include "nsIContent.h" #include "nsHTMLParts.h" #include "nsIViewManager.h" #include "nsIView.h" #include "nsIPresShell.h" #include "nsFrameNavigator.h" #include "nsCSSRendering.h" #include "nsIServiceManager.h" #include "nsBoxToBlockAdaptor.h" #include "nsILineIterator.h" #include "nsIFontMetrics.h" #include "nsHTMLContainerFrame.h" #include "nsWidgetsCID.h" #include "nsIWidget.h" #include "nsBlockFrame.h" #include "nsLayoutAtoms.h" static NS_DEFINE_IID(kWidgetCID, NS_CHILD_CID); //#define DEBUG_REFLOW //#define DEBUG_GROW #ifdef DEBUG_REFLOW PRInt32 gIndent2 = 0; void nsAdaptorAddIndents() { for(PRInt32 i=0; i < gIndent2; i++) { printf(" "); } } void nsAdaptorPrintReason(nsHTMLReflowState& aReflowState) { char* reflowReasonString; switch(aReflowState.reason) { case eReflowReason_Initial: reflowReasonString = "initial"; break; case eReflowReason_Resize: reflowReasonString = "resize"; break; case eReflowReason_Dirty: reflowReasonString = "dirty"; break; case eReflowReason_StyleChange: reflowReasonString = "stylechange"; break; case eReflowReason_Incremental: { nsReflowType type; aReflowState.reflowCommand->GetType(type); switch (type) { case eReflowType_StyleChanged: reflowReasonString = "incremental (StyleChanged)"; break; case eReflowType_ReflowDirty: reflowReasonString = "incremental (ReflowDirty)"; break; default: reflowReasonString = "incremental (Unknown)"; } } break; default: reflowReasonString = "unknown"; break; } printf("%s",reflowReasonString); } #endif nsBoxToBlockAdaptor::nsBoxToBlockAdaptor(nsIPresShell* aPresShell, nsIFrame* aFrame):nsBox(aPresShell) { mFrame = aFrame; mWasCollapsed = PR_FALSE; mStyleChange = PR_FALSE; mOverflow.width = 0; mOverflow.height = 0; mIncludeOverflow = PR_TRUE; mPresShell = aPresShell; NeedsRecalc(); // If we're wrapping a block (we may not be!), be sure the block // gets a space manager. void *block; mFrame->QueryInterface(kBlockFrameCID, &block); if (block) { mFrame->AddStateBits(NS_BLOCK_SPACE_MGR); } #ifdef DEBUG_waterson else { printf("*** nsBoxToBlockAdaptor: wrapping non-block frame "); nsFrame::ListTag(stdout, mFrame); printf("\n"); } #endif /* DEBUG_waterson */ } NS_IMETHODIMP nsBoxToBlockAdaptor::SetParentBox(nsIBox* aParent) { nsresult rv = nsBox::SetParentBox(aParent); nsIBox* parent = aParent; if (parent) { PRBool needsWidget = PR_FALSE; parent->ChildrenMustHaveWidgets(needsWidget); if (needsWidget) { nsHTMLContainerFrame::CreateViewForFrame(mFrame, nsnull, PR_TRUE); nsIView* view = mFrame->GetView(); if (!view->HasWidget()) view->CreateWidget(kWidgetCID); } } return rv; } PRBool nsBoxToBlockAdaptor::HasStyleChange() { return mStyleChange; } #ifdef DEBUG_LAYOUT void nsBoxToBlockAdaptor::GetBoxName(nsAutoString& aName) { nsIFrameDebug* frameDebug; nsAutoString name; if (NS_SUCCEEDED(mFrame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**)&frameDebug))) { frameDebug->GetFrameName(name); } aName = name; } #endif void nsBoxToBlockAdaptor::SetStyleChangeFlag(PRBool aDirty) { nsBox::SetStyleChangeFlag(aDirty); mStyleChange = PR_TRUE; } void* nsBoxToBlockAdaptor::operator new(size_t sz, nsIPresShell* aPresShell) CPP_THROW_NEW { return nsBoxLayoutState::Allocate(sz,aPresShell); } NS_IMETHODIMP nsBoxToBlockAdaptor::Recycle(nsIPresShell* aPresShell) { delete this; nsBoxLayoutState::RecycleFreedMemory(aPresShell, this); return NS_OK; } NS_IMETHODIMP nsBoxToBlockAdaptor::GetFrame(nsIFrame** aFrame) { *aFrame = mFrame; return NS_OK; } // Overridden to prevent the global delete from being called, since the memory // came out of an nsIArena instead of the global delete operator's heap. void nsBoxToBlockAdaptor::operator delete(void* aPtr, size_t sz) { nsBoxLayoutState::Free(aPtr, sz); } nsBoxToBlockAdaptor::~nsBoxToBlockAdaptor() { } NS_IMETHODIMP nsBoxToBlockAdaptor::NeedsRecalc() { SizeNeedsRecalc(mPrefSize); SizeNeedsRecalc(mMinSize); SizeNeedsRecalc(mMaxSize); SizeNeedsRecalc(mBlockPrefSize); SizeNeedsRecalc(mBlockMinSize); CoordNeedsRecalc(mFlex); CoordNeedsRecalc(mAscent); return NS_OK; } NS_IMETHODIMP nsBoxToBlockAdaptor::GetOverflow(nsSize& aOverflow) { aOverflow = mOverflow; return NS_OK; } NS_IMETHODIMP nsBoxToBlockAdaptor::SetIncludeOverflow(PRBool aInclude) { mIncludeOverflow = aInclude; return NS_OK; } static PRBool UseHTMLReflowConstraints(nsBoxToBlockAdaptor* aAdaptor, nsBoxLayoutState& aState) { nsSize constrainedSize; aState.GetScrolledBlockSizeConstraint(constrainedSize); if (constrainedSize.width < 0 || constrainedSize.height < 0) { return PR_FALSE; } nsIFrame* frame; aAdaptor->GetFrame(&frame); if (!frame) { return PR_FALSE; } nsIFrame* parentFrame = frame->GetParent(); if (!parentFrame) { return PR_FALSE; } if (parentFrame->GetType() != nsLayoutAtoms::scrollFrame) { return PR_FALSE; } return PR_TRUE; } NS_IMETHODIMP nsBoxToBlockAdaptor::RefreshSizeCache(nsBoxLayoutState& aState) { // Ok we need to compute our minimum, preferred, and maximum sizes. // 1) Maximum size. This is easy. Its infinite unless it is overloaded by CSS. // 2) Preferred size. This is a little harder. This is the size the block would be // if it were laid out on an infinite canvas. So we can get this by reflowing // the block with and INTRINSIC width and height. We can also do a nice optimization // for incremental reflow. If the reflow is incremental then we can pass a flag to // have the block compute the preferred width for us! Preferred height can just be // the minimum height; // 3) Minimum size. This is a toughy. We can pass the block a flag asking for the max element // size. That would give us the width. Unfortunately you can only ask for a maxElementSize // during an incremental reflow. So on other reflows we will just have to use 0. // The min height on the other hand is fairly easy we need to get the largest // line height. This can be done with the line iterator. // if we do have a reflow state nsresult rv = NS_OK; const nsHTMLReflowState* reflowState = aState.GetReflowState(); if (reflowState) { nsIPresContext* presContext = aState.GetPresContext(); nsReflowStatus status = NS_FRAME_COMPLETE; nsHTMLReflowMetrics desiredSize(PR_FALSE); nsReflowReason reason; PRBool useHTMLConstraints = UseHTMLReflowConstraints(this, aState); // See if we an set the max element size and return the reflow states new reason. Sometimes reflow states need to // be changed. Incremental dirty reflows targeted at us can be converted to Resize if we are not dirty. So make sure // we look at the reason returned. nsReflowPath *path = nsnull; PRBool canSetMaxElementWidth = CanSetMaxElementWidth(aState, reason, &path); NS_ASSERTION(reason != eReflowReason_Incremental || path, "HandleIncrementalReflow should have changed the reason to dirty."); // If we don't have any HTML constraints and its a resize, then nothing in the block // could have changed, so no refresh is necessary. if (!DoesNeedRecalc(mBlockPrefSize) && reason == eReflowReason_Resize && !useHTMLConstraints) return NS_OK; // get the old rect. nsRect oldRect = mFrame->GetRect(); // the rect we plan to size to. nsRect rect(oldRect); // if we can set the maxElementSize then // tell the metrics we want it. And also tell it we want // to compute the max width. This will allow us to get the min width and the pref width. if (canSetMaxElementWidth) { desiredSize.mFlags |= NS_REFLOW_CALC_MAX_WIDTH; desiredSize.mComputeMEW = PR_TRUE; } else { // if we can't set the maxElementSize. Then we must reflow // uncontrained. rect.width = NS_UNCONSTRAINEDSIZE; rect.height = NS_UNCONSTRAINEDSIZE; } if (useHTMLConstraints) { nsSize constrained; aState.GetScrolledBlockSizeConstraint(constrained); rect.width = constrained.width; rect.height = constrained.height; } // Create a child reflow state, fix-up the reason and the // incremental reflow path. nsHTMLReflowState childReflowState(*reflowState); childReflowState.reason = reason; childReflowState.path = path; // do the nasty. rv = Reflow(aState, presContext, desiredSize, childReflowState, status, rect.x, rect.y, rect.width, rect.height); nsRect newRect = mFrame->GetRect(); // make sure we draw any size change if (reason == eReflowReason_Incremental && (oldRect.width != newRect.width || oldRect.height != newRect.height)) { newRect.x = 0; newRect.y = 0; Redraw(aState, &newRect); } // if someone asked the nsBoxLayoutState to get the max size lets handle that. nscoord* stateMaxElementWidth = aState.GetMaxElementWidth(); // the max element size is the largest height and width if (stateMaxElementWidth) { if (mBlockMinSize.width > *stateMaxElementWidth) *stateMaxElementWidth = mBlockMinSize.width; } mBlockMinSize.height = 0; // if we can use the maxElmementSize then lets use it // if not then just use the desired. if (canSetMaxElementWidth) { mBlockPrefSize.width = desiredSize.mMaximumWidth; mBlockMinSize.width = desiredSize.mMaxElementWidth; // ok we need the max ascent of the items on the line. So to do this // ask the block for its line iterator. Get the max ascent. nsCOMPtr lines = do_QueryInterface(mFrame); if (lines) { mBlockMinSize.height = 0; int count = 0; nsIFrame* firstFrame = nsnull; PRInt32 framesOnLine; nsRect lineBounds; PRUint32 lineFlags; do { lines->GetLine(count, &firstFrame, &framesOnLine, lineBounds, &lineFlags); if (lineBounds.height > mBlockMinSize.height) mBlockMinSize.height = lineBounds.height; count++; } while(firstFrame); } mBlockPrefSize.height = mBlockMinSize.height; } else { mBlockPrefSize.width = desiredSize.width; mBlockPrefSize.height = desiredSize.height; // this sucks. We could not get the width. mBlockMinSize.width = 0; mBlockMinSize.height = desiredSize.height; } if (useHTMLConstraints) { // set the preferred metrics to exactly what the block asked for mBlockPrefSize.width = desiredSize.width; mBlockPrefSize.height = desiredSize.height; } mBlockAscent = desiredSize.ascent; #ifdef DEBUG_adaptor printf("min=(%d,%d), pref=(%d,%d), ascent=%d\n", mBlockMinSize.width, mBlockMinSize.height, mBlockPrefSize.width, mBlockPrefSize.height, mBlockAscent); #endif } return rv; } NS_IMETHODIMP nsBoxToBlockAdaptor::GetPrefSize(nsBoxLayoutState& aState, nsSize& aSize) { // If the size is cached, and there are no HTML constraints that we might // be depending on, then we just return the cached size. if (!DoesNeedRecalc(mPrefSize) && !UseHTMLReflowConstraints(this, aState)) { aSize = mPrefSize; return NS_OK; } aSize.width = 0; aSize.height = 0; PRBool isCollapsed = PR_FALSE; IsCollapsed(aState, isCollapsed); if (isCollapsed) { return NS_OK; } else { // get our size in CSS. PRBool completelyRedefined = nsIBox::AddCSSPrefSize(aState, this, mPrefSize); // Refresh our caches with new sizes. if (!completelyRedefined) { RefreshSizeCache(aState); mPrefSize = mBlockPrefSize; // notice we don't need to add our borders or padding // in. Thats because the block did it for us. // but we do need to add insets so debugging will work. AddInset(mPrefSize); nsIBox::AddCSSPrefSize(aState, this, mPrefSize); } } aSize = mPrefSize; return NS_OK; } NS_IMETHODIMP nsBoxToBlockAdaptor::GetMinSize(nsBoxLayoutState& aState, nsSize& aSize) { // Don't use the cache if we have HTMLReflowState constraints --- they might have changed if (!DoesNeedRecalc(mMinSize) && !UseHTMLReflowConstraints(this, aState)) { aSize = mMinSize; return NS_OK; } aSize.width = 0; aSize.height = 0; PRBool isCollapsed = PR_FALSE; IsCollapsed(aState, isCollapsed); if (isCollapsed) { return NS_OK; } else { // get our size in CSS. PRBool completelyRedefined = nsIBox::AddCSSMinSize(aState, this, mMinSize); // Refresh our caches with new sizes. if (!completelyRedefined) { RefreshSizeCache(aState); mMinSize = mBlockMinSize; AddInset(mMinSize); nsIBox::AddCSSMinSize(aState, this, mMinSize); } } aSize = mMinSize; return NS_OK; } NS_IMETHODIMP nsBoxToBlockAdaptor::GetMaxSize(nsBoxLayoutState& aState, nsSize& aSize) { // Don't use the cache if we have HTMLReflowState constraints --- they might have changed if (!DoesNeedRecalc(mMaxSize) && !UseHTMLReflowConstraints(this, aState)) { aSize = mMaxSize; return NS_OK; } aSize.width = NS_INTRINSICSIZE; aSize.height = NS_INTRINSICSIZE; PRBool isCollapsed = PR_FALSE; IsCollapsed(aState, isCollapsed); if (isCollapsed) { return NS_OK; } else { mMaxSize.width = NS_INTRINSICSIZE; mMaxSize.height = NS_INTRINSICSIZE; nsBox::GetMaxSize(aState, mMaxSize); } aSize = mMaxSize; return NS_OK; } NS_IMETHODIMP nsBoxToBlockAdaptor::GetFlex(nsBoxLayoutState& aState, nscoord& aFlex) { if (!DoesNeedRecalc(mFlex)) { aFlex = mFlex; return NS_OK; } mFlex = 0; nsBox::GetFlex(aState, mFlex); aFlex = mFlex; return NS_OK; } NS_IMETHODIMP nsBoxToBlockAdaptor::GetAscent(nsBoxLayoutState& aState, nscoord& aAscent) { if (!DoesNeedRecalc(mAscent)) { aAscent = mAscent; return NS_OK; } PRBool isCollapsed = PR_FALSE; IsCollapsed(aState, isCollapsed); if (isCollapsed) { mAscent = 0; } else { // Refresh our caches with new sizes. RefreshSizeCache(aState); mAscent = mBlockAscent; nsMargin m(0,0,0,0); GetInset(m); mAscent += m.top; } return NS_OK; } NS_IMETHODIMP nsBoxToBlockAdaptor::IsCollapsed(nsBoxLayoutState& aState, PRBool& aCollapsed) { return nsBox::IsCollapsed(aState, aCollapsed); } nsresult nsBoxToBlockAdaptor::DoLayout(nsBoxLayoutState& aState) { nsRect ourRect(0,0,0,0); GetBounds(ourRect); const nsHTMLReflowState* reflowState = aState.GetReflowState(); nsIPresContext* presContext = aState.GetPresContext(); nsReflowStatus status = NS_FRAME_COMPLETE; nsHTMLReflowMetrics desiredSize(PR_FALSE); nsresult rv = NS_OK; if (reflowState) { nscoord* currentMEW = aState.GetMaxElementWidth(); if (currentMEW) { desiredSize.mComputeMEW = PR_TRUE; } rv = Reflow(aState, presContext, desiredSize, *reflowState, status, ourRect.x, ourRect.y, ourRect.width, ourRect.height); if (currentMEW && desiredSize.mMaxElementWidth > *currentMEW) { *currentMEW = desiredSize.mMaxElementWidth; } PRBool collapsed = PR_FALSE; IsCollapsed(aState, collapsed); if (collapsed) { mFrame->SetSize(nsSize(0, 0)); } else { // if our child needs to be bigger. This might happend with // wrapping text. There is no way to predict its height until we // reflow it. Now that we know the height reshuffle upward. if (desiredSize.width > ourRect.width || desiredSize.height > ourRect.height) { #ifdef DEBUG_GROW DumpBox(stdout); printf(" GREW from (%d,%d) -> (%d,%d)\n", ourRect.width, ourRect.height, desiredSize.width, desiredSize.height); #endif if (desiredSize.width > ourRect.width) ourRect.width = desiredSize.width; if (desiredSize.height > ourRect.height) ourRect.height = desiredSize.height; } // ensure our size is what we think is should be. Someone could have // reset the frame to be smaller or something dumb like that. mFrame->SetSize(nsSize(ourRect.width, ourRect.height)); } } SyncLayout(aState); return rv; } // Truncate the reflow path by pruning the subtree containing the // specified frame. This ensures that we don't accidentally // incrementally reflow a frame twice. // XXXwaterson We could be more efficient by remembering the parent in // FindReflowPathFor. static void PruneReflowPathFor(nsIFrame *aFrame, nsReflowPath *aReflowPath) { nsReflowPath::iterator iter, end = aReflowPath->EndChildren(); for (iter = aReflowPath->FirstChild(); iter != end; ++iter) { if (*iter == aFrame) { aReflowPath->Remove(iter); break; } PruneReflowPathFor(aFrame, iter.get()); } } nsresult nsBoxToBlockAdaptor::Reflow(nsBoxLayoutState& aState, nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus, nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight, PRBool aMoveFrame) { DO_GLOBAL_REFLOW_COUNT("nsBoxToBlockAdaptor", aReflowState.reason); #ifdef DEBUG_REFLOW nsAdaptorAddIndents(); printf("Reflowing: "); nsFrame::ListTag(stdout, mFrame); printf("\n"); gIndent2++; #endif //printf("width=%d, height=%d\n", aWidth, aHeight); /* nsIBox* parent; GetParentBox(&parent); nsIFrame* frame; parent->GetFrame(&frame); // if (frame->GetStateBits() & NS_STATE_CURRENTLY_IN_DEBUG) // printf("In debug\n"); */ aStatus = NS_FRAME_COMPLETE; PRBool redrawAfterReflow = PR_FALSE; PRBool needsReflow = PR_FALSE; PRBool redrawNow = PR_FALSE; nsReflowReason reason; nsReflowPath *path = nsnull; HandleIncrementalReflow(aState, aReflowState, reason, &path, redrawNow, needsReflow, redrawAfterReflow, aMoveFrame); if (redrawNow) Redraw(aState); // if we don't need a reflow then // lets see if we are already that size. Yes? then don't even reflow. We are done. if (!needsReflow) { if (aWidth != NS_INTRINSICSIZE && aHeight != NS_INTRINSICSIZE) { // if the new calculated size has a 0 width or a 0 height if ((mLastSize.width == 0 || mLastSize.height == 0) && (aWidth == 0 || aHeight == 0)) { needsReflow = PR_FALSE; aDesiredSize.width = aWidth; aDesiredSize.height = aHeight; mFrame->SetSize(nsSize(aDesiredSize.width, aDesiredSize.height)); } else { aDesiredSize.width = mLastSize.width; aDesiredSize.height = mLastSize.height; // remove the margin. The rect of our child does not include it but our calculated size does. nscoord calcWidth = aWidth; nscoord calcHeight = aHeight; // don't reflow if we are already the right size if (mLastSize.width == calcWidth && mLastSize.height == calcHeight) needsReflow = PR_FALSE; else needsReflow = PR_TRUE; } } else { // if the width or height are intrinsic alway reflow because // we don't know what it should be. needsReflow = PR_TRUE; } } // ok now reflow the child into the spacers calculated space if (needsReflow) { nsMargin border(0,0,0,0); GetBorderAndPadding(border); aDesiredSize.width = 0; aDesiredSize.height = 0; nsSize size(aWidth, aHeight); // create a reflow state to tell our child to flow at the given size. if (size.height != NS_INTRINSICSIZE) { size.height -= (border.top + border.bottom); if (size.height < 0) size.height = 0; } if (size.width != NS_INTRINSICSIZE) { size.width -= (border.left + border.right); if (size.width < 0) size.width = 0; } // Create with a reason of resize, and then change the `reason' // and `path' appropriately (since for incremental reflow, we'll // be mangling it so completely). nsHTMLReflowState reflowState(aPresContext, aReflowState, mFrame, nsSize(size.width, NS_INTRINSICSIZE), eReflowReason_Resize); reflowState.reason = reason; reflowState.path = path; // XXX this needs to subtract out the border and padding of mFrame since it is content size reflowState.mComputedWidth = size.width; reflowState.mComputedHeight = size.height; // if we were marked for style change. // 1) see if we are just supposed to do a resize if so convert to a style change. Kill 2 birds // with 1 stone. // 2) If the command is incremental. See if its style change. If it is everything is ok if not // we need to do a second reflow with the style change. // XXXwaterson This logic seems _very_ squirrely. if (mStyleChange) { if (reflowState.reason == eReflowReason_Resize) { // maxElementSize does not work on style change reflows. // so remove it if set. // XXXwaterson why doesn't MES computation work with a style change reflow? aDesiredSize.mComputeMEW = PR_FALSE; reflowState.reason = eReflowReason_StyleChange; } else if (reason == eReflowReason_Incremental) { PRBool reflowChild = PR_TRUE; if (path->mReflowCommand && path->FirstChild() == path->EndChildren()) { // There's an incremental reflow targeted directly at our // frame, and our frame only (i.e., none of our descendants // are targets). nsReflowType type; path->mReflowCommand->GetType(type); if (type == eReflowType_StyleChanged) reflowChild = PR_FALSE; } if (reflowChild) { #ifdef DEBUG_waterson printf("*** nsBoxToBlockAdaptor::Reflow: performing extra reflow on child frame\n"); #endif #ifdef DEBUG_REFLOW nsAdaptorAddIndents(); printf("Size=(%d,%d)\n",reflowState.mComputedWidth, reflowState.mComputedHeight); nsAdaptorAddIndents(); nsAdaptorPrintReason(reflowState); printf("\n"); #endif mFrame->WillReflow(aPresContext); mFrame->Reflow(aPresContext, aDesiredSize, reflowState, aStatus); mFrame->DidReflow(aPresContext, &reflowState, NS_FRAME_REFLOW_FINISHED); reflowState.mComputedWidth = aDesiredSize.width - (border.left + border.right); reflowState.availableWidth = reflowState.mComputedWidth; reflowState.reason = eReflowReason_StyleChange; reflowState.path = nsnull; } } mStyleChange = PR_FALSE; } #ifdef DEBUG_REFLOW nsAdaptorAddIndents(); printf("Size=(%d,%d)\n",reflowState.mComputedWidth, reflowState.mComputedHeight); nsAdaptorAddIndents(); nsAdaptorPrintReason(reflowState); printf("\n"); #endif // place the child and reflow mFrame->WillReflow(aPresContext); mFrame->Reflow(aPresContext, aDesiredSize, reflowState, aStatus); NS_ASSERTION(NS_FRAME_IS_COMPLETE(aStatus), "bad status"); // Save the ascent. (bug 103925) PRBool isCollapsed = PR_FALSE; IsCollapsed(aState, isCollapsed); if (isCollapsed) { mAscent = 0; } else { mAscent = aDesiredSize.ascent; } // printf("width: %d, height: %d\n", aDesiredSize.mCombinedArea.width, aDesiredSize.mCombinedArea.height); // see if the overflow option is set. If it is then if our child's bounds overflow then // we will set the child's rect to include the overflow size. if (mFrame->GetStateBits() & NS_FRAME_OUTSIDE_CHILDREN) { // make sure we store the overflow size mOverflow.width = aDesiredSize.mOverflowArea.width; mOverflow.height = aDesiredSize.mOverflowArea.height; // include the overflow size in our child's rect? if (mIncludeOverflow) { //printf("OutsideChildren width=%d, height=%d\n", aDesiredSize.mOverflowArea.width, aDesiredSize.mOverflowArea.height); aDesiredSize.width = aDesiredSize.mOverflowArea.width; if (aDesiredSize.width <= aWidth) aDesiredSize.height = aDesiredSize.mOverflowArea.height; else { if (aDesiredSize.width > aWidth) { reflowState.mComputedWidth = aDesiredSize.width - (border.left + border.right); reflowState.availableWidth = reflowState.mComputedWidth; reflowState.reason = eReflowReason_Resize; reflowState.path = nsnull; mFrame->DidReflow(aPresContext, &reflowState, NS_FRAME_REFLOW_FINISHED); #ifdef DEBUG_REFLOW nsAdaptorAddIndents(); nsAdaptorPrintReason(reflowState); printf("\n"); #endif mFrame->WillReflow(aPresContext); mFrame->Reflow(aPresContext, aDesiredSize, reflowState, aStatus); if (mFrame->GetStateBits() & NS_FRAME_OUTSIDE_CHILDREN) aDesiredSize.height = aDesiredSize.mOverflowArea.height; } } } } else { mOverflow.width = aDesiredSize.width; mOverflow.height = aDesiredSize.height; } if (redrawAfterReflow) { nsIFrame* frame = nsnull; GetFrame(&frame); nsRect r = frame->GetRect(); r.width = aDesiredSize.width; r.height = aDesiredSize.height; Redraw(aState, &r); } PRBool changedSize = PR_FALSE; if (mLastSize.width != aDesiredSize.width || mLastSize.height != aDesiredSize.height) changedSize = PR_TRUE; PRUint32 layoutFlags; aState.GetLayoutFlags(layoutFlags); nsContainerFrame::FinishReflowChild(mFrame, aPresContext, &reflowState, aDesiredSize, aX, aY, layoutFlags | NS_FRAME_NO_MOVE_FRAME); } else { aDesiredSize.ascent = mBlockAscent; } // Clip the path we just reflowed, so that we don't incrementally // reflow it again: subsequent reflows will be treated as resize // reflows. if (path) PruneReflowPathFor(path->mFrame, aReflowState.path); #ifdef DEBUG_REFLOW if (aHeight != NS_INTRINSICSIZE && aDesiredSize.height != aHeight) { nsAdaptorAddIndents(); printf("*****got taller!*****\n"); } if (aWidth != NS_INTRINSICSIZE && aDesiredSize.width != aWidth) { nsAdaptorAddIndents(); printf("*****got wider!******\n"); } #endif if (aWidth == NS_INTRINSICSIZE) aWidth = aDesiredSize.width; if (aHeight == NS_INTRINSICSIZE) aHeight = aDesiredSize.height; mLastSize.width = aDesiredSize.width; mLastSize.height = aDesiredSize.height; #ifdef DEBUG_REFLOW gIndent2--; #endif NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); return NS_OK; } // Look for aFrame in the specified reflow path's tree, returning the // reflow path node corresponding to the frame if we find it. static nsReflowPath * FindReflowPathFor(nsIFrame *aFrame, nsReflowPath *aReflowPath) { nsReflowPath::iterator iter, end = aReflowPath->EndChildren(); for (iter = aReflowPath->FirstChild(); iter != end; ++iter) { if (*iter == aFrame) return iter.get(); nsReflowPath *subtree = FindReflowPathFor(aFrame, iter.get()); if (subtree) return subtree; } return nsnull; } void nsBoxToBlockAdaptor::HandleIncrementalReflow(nsBoxLayoutState& aState, const nsHTMLReflowState& aReflowState, nsReflowReason& aReason, nsReflowPath** aReflowPath, PRBool& aRedrawNow, PRBool& aNeedsReflow, PRBool& aRedrawAfterReflow, PRBool& aMoveFrame) { nsFrameState childState = mFrame->GetStateBits(); aReason = aReflowState.reason; // handle or different types of reflow switch(aReason) { // if the child we are reflowing is the child we popped off the incremental // reflow chain then we need to reflow it no matter what. // if its not the child we got from the reflow chain then this child needs reflow // because as a side effect of the incremental child changing size it needs to be resized. // This will happen a lot when a box that contains 2 children with different flexibilities // if on child gets bigger the other is affected because it is proprotional to the first. // so it might need to be resized. But we don't need to reflow it. If it is already the // needed size then we will do nothing. case eReflowReason_Incremental: { // Grovel through the reflow path's children to find the path // that corresponds to the current frame. If we can't find a // child, then we'll convert the reflow to a dirty reflow, // below. nsReflowPath *path = FindReflowPathFor(mFrame, aReflowState.path); if (path) { aNeedsReflow = PR_TRUE; // Return the path that we've found so that HTML incremental // reflow can proceed normally. if (aReflowPath) *aReflowPath = path; // if we hit the target then we have used up the chain. // next time a layout break; } // fall into dirty if the incremental child was use. It should be treated as a } // if its dirty then see if the child we want to reflow is dirty. If it is then // mark it as needing to be reflowed. case eReflowReason_Dirty: { // XXX nsBlockFrames don't seem to be able to handle a reason of Dirty. So we // send down a resize instead. If we did send down the dirty we would have wrapping problems. If you // look at the main page it will initially come up ok but will have a unneeded horizontal // scrollbar if you resize it will fix it self. The real fix is to fix block frame but // this will fix it for beta3. if (childState & NS_FRAME_FIRST_REFLOW) aReason = eReflowReason_Initial; else aReason = eReflowReason_Resize; // get the frame state to see if it needs reflow aNeedsReflow = mStyleChange || (childState & NS_FRAME_IS_DIRTY) || (childState & NS_FRAME_HAS_DIRTY_CHILDREN); // but of course by definition dirty reflows are supposed to redraw so // lets signal that we need to do that. We want to do it after as well because // the object may have changed size. if (aNeedsReflow) { aRedrawNow = PR_TRUE; aRedrawAfterReflow = PR_TRUE; //printf("Redrawing!!!/n"); } } break; // if the a resize reflow then it doesn't need to be reflowed. Only if the size is different // from the new size would we actually do a reflow case eReflowReason_Resize: // blocks sometimes send resizes even when its children are dirty! We need to make sure we // repair in these cases. So check the flags here. aNeedsReflow = mStyleChange || (childState & NS_FRAME_IS_DIRTY) || (childState & NS_FRAME_HAS_DIRTY_CHILDREN); break; // if its an initial reflow we must place the child. // otherwise we might think it was already placed when it wasn't case eReflowReason_Initial: aMoveFrame = PR_TRUE; aNeedsReflow = PR_TRUE; break; default: aNeedsReflow = PR_TRUE; } } PRBool nsBoxToBlockAdaptor::GetWasCollapsed(nsBoxLayoutState& aState) { return mWasCollapsed; } void nsBoxToBlockAdaptor::SetWasCollapsed(nsBoxLayoutState& aState, PRBool aCollapsed) { mWasCollapsed = aCollapsed; } PRBool nsBoxToBlockAdaptor::CanSetMaxElementWidth(nsBoxLayoutState& aState, nsReflowReason& aReason, nsReflowPath **aReflowPath) { PRBool redrawAfterReflow = PR_FALSE; PRBool needsReflow = PR_FALSE; PRBool redrawNow = PR_FALSE; PRBool move = PR_TRUE; const nsHTMLReflowState* reflowState = aState.GetReflowState(); HandleIncrementalReflow(aState, *reflowState, aReason, aReflowPath, redrawNow, needsReflow, redrawAfterReflow, move); // only incremental reflows can handle maxelementsize being set. if (reflowState->reason == eReflowReason_Incremental) { nsReflowPath *path = *aReflowPath; if (path && path->mReflowCommand) { // MaxElement doesn't work on style change reflows.. :-( // XXXwaterson why? nsReflowType type; path->mReflowCommand->GetType(type); if (type == eReflowType_StyleChanged) return PR_FALSE; } return PR_TRUE; } return PR_FALSE; } NS_IMPL_ADDREF_INHERITED(nsBoxToBlockAdaptor, nsBox) NS_IMPL_RELEASE_INHERITED(nsBoxToBlockAdaptor, nsBox) // // QueryInterface // NS_INTERFACE_MAP_BEGIN(nsBoxToBlockAdaptor) NS_INTERFACE_MAP_ENTRY(nsIBoxToBlockAdaptor) if (NS_SUCCEEDED(mFrame->QueryInterface(aIID, aInstancePtr))) return NS_OK; else NS_INTERFACE_MAP_END_INHERITING(nsBox)