diff --git a/layout/generic/nsContainerFrame.cpp b/layout/generic/nsContainerFrame.cpp new file mode 100644 index 000000000000..f89b29ce1fdc --- /dev/null +++ b/layout/generic/nsContainerFrame.cpp @@ -0,0 +1,700 @@ +/* -*- 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 "nsContainerFrame.h" +#include "nsIContent.h" +#include "nsIPresContext.h" +#include "nsIRenderingContext.h" +#include "nsIRunaround.h" +#include "nsISpaceManager.h" +#include "nsIStyleContext.h" +#include "nsRect.h" +#include "nsPoint.h" +#include "nsGUIEvent.h" +#include "nsStyleConsts.h" +#include "nsIView.h" +#include "nsVoidArray.h" +#include "nsISizeOfHandler.h" + +#ifdef NS_DEBUG +#undef NOISY +#else +#undef NOISY +#endif + +nsContainerFrame::nsContainerFrame(nsIContent* aContent, nsIFrame* aParent) + : nsSplittableFrame(aContent, aParent) +{ +} + +nsContainerFrame::~nsContainerFrame() +{ +} + +NS_IMETHODIMP +nsContainerFrame::SizeOf(nsISizeOfHandler* aHandler) const +{ + aHandler->Add(sizeof(*this)); + nsContainerFrame::SizeOfWithoutThis(aHandler); + return NS_OK; +} + +NS_IMETHODIMP +nsContainerFrame::Init(nsIPresContext& aPresContext, nsIFrame* aChildList) +{ + NS_PRECONDITION(nsnull == mFirstChild, "already initialized"); + + mFirstChild = aChildList; + return NS_OK; +} + +NS_IMETHODIMP +nsContainerFrame::DeleteFrame(nsIPresContext& aPresContext) +{ + // Delete our child frames before doing anything else. In particular + // we do all of this before our base class releases it's hold on the + // view. + for (nsIFrame* child = mFirstChild; child; ) { + mFirstChild = nsnull; // XXX hack until HandleEvent is not called until after destruction + nsIFrame* nextChild; + + child->GetNextSibling(nextChild); + child->DeleteFrame(aPresContext); + child = nextChild; + } + + nsFrame::DeleteFrame(aPresContext); + + return NS_OK; +} + +void +nsContainerFrame::SizeOfWithoutThis(nsISizeOfHandler* aHandler) const +{ + nsSplittableFrame::SizeOfWithoutThis(aHandler); + for (nsIFrame* child = mFirstChild; child; ) { + child->SizeOf(aHandler); + child->GetNextSibling(child); + } +} + +void +nsContainerFrame::PrepareContinuingFrame(nsIPresContext& aPresContext, + nsIFrame* aParent, + nsIStyleContext* aStyleContext, + nsContainerFrame* aContFrame) +{ + // Append the continuing frame to the flow + aContFrame->AppendToFlow(this); + aContFrame->SetStyleContext(&aPresContext, aStyleContext); +} + +NS_METHOD +nsContainerFrame::DidReflow(nsIPresContext& aPresContext, + nsDidReflowStatus aStatus) +{ + NS_FRAME_TRACE_MSG(NS_FRAME_TRACE_CALLS, + ("enter nsContainerFrame::DidReflow: status=%d", + aStatus)); + if (NS_FRAME_REFLOW_FINISHED == aStatus) { + nsIFrame* kid; + FirstChild(kid); + while (nsnull != kid) { + kid->DidReflow(aPresContext, aStatus); + kid->GetNextSibling(kid); + } + } + + NS_FRAME_TRACE_OUT("nsContainerFrame::DidReflow"); + + // Let nsFrame position and size our view (if we have one), and clear + // the NS_FRAME_IN_REFLOW bit + return nsFrame::DidReflow(aPresContext, aStatus); +} + +///////////////////////////////////////////////////////////////////////////// +// Child frame enumeration + +NS_METHOD nsContainerFrame::FirstChild(nsIFrame*& aFirstChild) const +{ + aFirstChild = mFirstChild; + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Painting + +NS_METHOD nsContainerFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + PaintChildren(aPresContext, aRenderingContext, aDirtyRect); + return NS_OK; +} + +// aDirtyRect is in our coordinate system +// child rect's are also in our coordinate system +void nsContainerFrame::PaintChildren(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + // Set clip rect so that children don't leak out of us + const nsStyleDisplay* disp = + (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); + PRBool hidden = PR_FALSE; + if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) { + aRenderingContext.PushState(); + aRenderingContext.SetClipRect(nsRect(0, 0, mRect.width, mRect.height), + nsClipCombine_kIntersect); + hidden = PR_TRUE; + } + + // XXX reminder: use the coordinates in the dirty rect to figure out + // which set of children are impacted and only do the intersection + // work for them. In addition, stop when we no longer overlap. + + nsIFrame* kid = mFirstChild; + while (nsnull != kid) { + nsIView *pView; + + kid->GetView(pView); + if (nsnull == pView) { + nsRect kidRect; + kid->GetRect(kidRect); + nsRect damageArea; +#ifdef NS_DEBUG + PRBool overlap = PR_FALSE; + if (nsIFrame::GetShowFrameBorders() && + ((kidRect.width == 0) || (kidRect.height == 0))) { + nscoord xmost = aDirtyRect.XMost(); + nscoord ymost = aDirtyRect.YMost(); + if ((aDirtyRect.x <= kidRect.x) && (kidRect.x < xmost) && + (aDirtyRect.y <= kidRect.y) && (kidRect.y < ymost)) { + overlap = PR_TRUE; + } + } + else { + overlap = damageArea.IntersectRect(aDirtyRect, kidRect); + } +#else + PRBool overlap = damageArea.IntersectRect(aDirtyRect, kidRect); +#endif + if (overlap) { + // Translate damage area into kid's coordinate system + nsRect kidDamageArea(damageArea.x - kidRect.x, + damageArea.y - kidRect.y, + damageArea.width, damageArea.height); + aRenderingContext.PushState(); + aRenderingContext.Translate(kidRect.x, kidRect.y); + kid->Paint(aPresContext, aRenderingContext, kidDamageArea); +#ifdef NS_DEBUG + if (nsIFrame::GetShowFrameBorders() && + (0 != kidRect.width) && (0 != kidRect.height)) { + nsIView* view; + GetView(view); + if (nsnull != view) { + aRenderingContext.SetColor(NS_RGB(0,0,255)); + } + else { + aRenderingContext.SetColor(NS_RGB(255,0,0)); + } + aRenderingContext.DrawRect(0, 0, kidRect.width, kidRect.height); + } +#endif + aRenderingContext.PopState(); + } + } + kid->GetNextSibling(kid); + } + + if (hidden) { + aRenderingContext.PopState(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Events + +NS_METHOD nsContainerFrame::HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus) +{ + aEventStatus = nsEventStatus_eIgnore; + + nsIFrame* kid; + FirstChild(kid); + while (nsnull != kid) { + nsRect kidRect; + kid->GetRect(kidRect); + if (kidRect.Contains(aEvent->point)) { + aEvent->point.MoveBy(-kidRect.x, -kidRect.y); + kid->HandleEvent(aPresContext, aEvent, aEventStatus); + aEvent->point.MoveBy(kidRect.x, kidRect.y); + break; + } + kid->GetNextSibling(kid); + } + + return NS_OK; +} + +NS_METHOD nsContainerFrame::GetCursorAndContentAt(nsIPresContext& aPresContext, + const nsPoint& aPoint, + nsIFrame** aFrame, + nsIContent** aContent, + PRInt32& aCursor) +{ + aCursor = NS_STYLE_CURSOR_INHERIT; + *aContent = mContent; + + nsIFrame* kid; + FirstChild(kid); + nsPoint tmp; + while (nsnull != kid) { + nsRect kidRect; + kid->GetRect(kidRect); + if (kidRect.Contains(aPoint)) { + tmp.MoveTo(aPoint.x - kidRect.x, aPoint.y - kidRect.y); + kid->GetCursorAndContentAt(aPresContext, tmp, aFrame, aContent, aCursor); + break; + } + kid->GetNextSibling(kid); + } + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Helper member functions + +/** + * Reflow a child frame and return the status of the reflow. If the + * child is complete and it has next-in-flows (it was a splittable child) + * then delete the next-in-flows. + */ +nsReflowStatus nsContainerFrame::ReflowChild(nsIFrame* aKidFrame, + nsIPresContext* aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState) +{ + nsReflowStatus status; + + NS_PRECONDITION(aReflowState.frame == aKidFrame, "bad reflow state"); +#ifdef NS_DEBUG + nsFrameState kidFrameState; + + aKidFrame->GetFrameState(kidFrameState); + NS_ASSERTION(kidFrameState & NS_FRAME_IN_REFLOW, "kid frame is not in reflow"); +#endif + aKidFrame->Reflow(*aPresContext, aDesiredSize, aReflowState, status); + + if (NS_FRAME_IS_COMPLETE(status)) { + nsIFrame* kidNextInFlow; + + aKidFrame->GetNextInFlow(kidNextInFlow); + if (nsnull != kidNextInFlow) { + // Remove all of the childs next-in-flows. Make sure that we ask + // the right parent to do the removal (it's possible that the + // parent is not this because we are executing pullup code) + nsIFrame* parent; + aKidFrame->GetGeometricParent(parent); + ((nsContainerFrame*)parent)->DeleteChildsNextInFlow(*aPresContext, aKidFrame); + } + } + return status; +} + +/** + * 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 + * + * @param aChild child this child's next-in-flow + * @return PR_TRUE if successful and PR_FALSE otherwise + */ +PRBool +nsContainerFrame::DeleteChildsNextInFlow(nsIPresContext& aPresContext, nsIFrame* aChild) +{ + NS_PRECONDITION(IsChild(aChild), "bad geometric parent"); + + nsIFrame* nextInFlow; + nsContainerFrame* parent; + + aChild->GetNextInFlow(nextInFlow); + NS_PRECONDITION(nsnull != nextInFlow, "null next-in-flow"); + nextInFlow->GetGeometricParent((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); + } + +#ifdef NS_DEBUG + PRInt32 childCount; + nsIFrame* firstChild; + + nextInFlow->FirstChild(firstChild); + childCount = LengthOf(firstChild); + + if ((0 != childCount) || (nsnull != firstChild)) { + nsIFrame* top = nextInFlow; + for (;;) { + nsIFrame* parent; + top->GetGeometricParent(parent); + if (nsnull == parent) { + break; + } + top = parent; + } + top->List(); + } + NS_ASSERTION((0 == childCount) && (nsnull == firstChild), + "deleting !empty next-in-flow"); +#endif + + // Disconnect the next-in-flow from the flow list + nextInFlow->BreakFromPrevFlow(); + + // Take the next-in-flow out of the parent's child list + if (parent->mFirstChild == nextInFlow) { + nextInFlow->GetNextSibling(parent->mFirstChild); + + } 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) + NS_ASSERTION(((nsContainerFrame*)parent)->IsChild(aChild), "screwy flow"); + aChild->GetNextSibling(nextSibling); + NS_ASSERTION(nextSibling == nextInFlow, "unexpected sibling"); + + nextInFlow->GetNextSibling(nextSibling); + aChild->SetNextSibling(nextSibling); + } + + // Delete the next-in-flow frame and adjust its parents child count + WillDeleteNextInFlowFrame(nextInFlow); + nextInFlow->DeleteFrame(aPresContext); + +#ifdef NS_DEBUG + aChild->GetNextInFlow(nextInFlow); + NS_POSTCONDITION(nsnull == nextInFlow, "non null next-in-flow"); +#endif + return PR_TRUE; +} + +void nsContainerFrame::WillDeleteNextInFlowFrame(nsIFrame* aNextInFlow) +{ +} + +/** + * Push aFromChild and its next siblings to the next-in-flow. Change the + * geometric parent of each frame that's pushed. If there is no next-in-flow + * the frames are placed on the overflow list (and the geometric parent is + * left unchanged). + * + * Updates the next-in-flow's child count. Does not update the + * pusher's child count. + * + * @param aFromChild the first child frame to push. It is disconnected from + * aPrevSibling + * @param aPrevSibling aFromChild's previous sibling. Must not be null. It's + * an error to push a parent's first child frame + */ +void nsContainerFrame::PushChildren(nsIFrame* aFromChild, nsIFrame* aPrevSibling) +{ + NS_PRECONDITION(nsnull != aFromChild, "null pointer"); + NS_PRECONDITION(nsnull != aPrevSibling, "pushing first child"); +#ifdef NS_DEBUG + nsIFrame* prevNextSibling; + + aPrevSibling->GetNextSibling(prevNextSibling); + NS_PRECONDITION(prevNextSibling == aFromChild, "bad prev sibling"); +#endif + + // Disconnect aFromChild from its previous sibling + aPrevSibling->SetNextSibling(nsnull); + + // Do we have a next-in-flow? + nsContainerFrame* nextInFlow = (nsContainerFrame*)mNextInFlow; + if (nsnull != nextInFlow) { + PRInt32 numChildren = 0; + nsIFrame* lastChild = nsnull; + + // Compute the number of children being pushed, and for each child change + // its geometric parent. Remember the last child + for (nsIFrame* f = aFromChild; nsnull != f; f->GetNextSibling(f)) { + numChildren++; +#ifdef NOISY + printf(" "); + ((nsFrame*)f)->ListTag(stdout); + printf("\n"); +#endif + lastChild = f; + f->SetGeometricParent(nextInFlow); + + nsIFrame* contentParent; + + f->GetContentParent(contentParent); + if (this == contentParent) { + f->SetContentParent(nextInFlow); + } + } + NS_ASSERTION(numChildren > 0, "no children to push"); + + // Prepend the frames to our next-in-flow's child list + lastChild->SetNextSibling(nextInFlow->mFirstChild); + nextInFlow->mFirstChild = aFromChild; + + } else { + // Add the frames to our overflow list + NS_ASSERTION(nsnull == mOverflowList, "bad overflow list"); +#ifdef NOISY + ListTag(stdout); + printf(": pushing kids to my overflow list\n"); +#endif + mOverflowList = aFromChild; + } +} + +/** + * Moves any frames on the overflwo lists (the prev-in-flow's overflow list and + * the receiver's overflow list) to the child list. + * + * Updates this frame's child count and content mapping. + * + * @return PR_TRUE if any frames were moved and PR_FALSE otherwise + */ +PRBool nsContainerFrame::MoveOverflowToChildList() +{ + PRBool result = PR_FALSE; + + // Check for an overflow list with our prev-in-flow + nsContainerFrame* prevInFlow = (nsContainerFrame*)mPrevInFlow; + if (nsnull != prevInFlow) { + if (nsnull != prevInFlow->mOverflowList) { + NS_ASSERTION(nsnull == mFirstChild, "bad overflow list"); + AppendChildren(prevInFlow->mOverflowList); + prevInFlow->mOverflowList = nsnull; + result = PR_TRUE; + } + } + + // It's also possible that we have an overflow list for ourselves + if (nsnull != mOverflowList) { + NS_ASSERTION(nsnull != mFirstChild, "overflow list but no mapped children"); + AppendChildren(mOverflowList, PR_FALSE); + mOverflowList = nsnull; + result = PR_TRUE; + } + + return result; +} + +/** + * Append child list starting at aChild to this frame's child list. Used for + * processing of the overflow list. + * + * Updates this frame's child count and content mapping. + * + * @param aChild the beginning of the child list + * @param aSetParent if true each child's geometric (and content parent if + * they're the same) parent is set to this frame. + */ +void nsContainerFrame::AppendChildren(nsIFrame* aChild, PRBool aSetParent) +{ + // Link the frames into our child frame list + if (nsnull == mFirstChild) { + // We have no children so aChild becomes the first child + mFirstChild = aChild; + } else { + nsIFrame* lastChild = LastFrame(mFirstChild); + lastChild->SetNextSibling(aChild); + } + + // Update our child count and last content offset + nsIFrame* lastChild; + for (nsIFrame* f = aChild; nsnull != f; f->GetNextSibling(f)) { + lastChild = f; + + // Reset the geometric parent if requested + if (aSetParent) { + nsIFrame* geometricParent; + nsIFrame* contentParent; + + f->GetGeometricParent(geometricParent); + f->GetContentParent(contentParent); + if (contentParent == geometricParent) { + f->SetContentParent(this); + } + f->SetGeometricParent(this); + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// Debugging + +NS_METHOD nsContainerFrame::List(FILE* out, PRInt32 aIndent, nsIListFilter *aFilter) const +{ + // if a filter is present, only output this frame if the filter says we should + nsIAtom* tag; + nsAutoString tagString; + mContent->GetTag(tag); + if (tag != nsnull) + tag->ToString(tagString); + PRBool outputMe = (PRBool)((nsnull==aFilter) || (PR_TRUE==aFilter->OutputTag(&tagString))); + if (PR_TRUE==outputMe) + { + // Indent + for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out); + + // Output the tag + ListTag(out); + nsIView* view; + GetView(view); + if (nsnull != view) { + fprintf(out, " [view=%p]", view); + } + + if (nsnull != mPrevInFlow) { + fprintf(out, "prev-in-flow=%p ", mPrevInFlow); + } + if (nsnull != mNextInFlow) { + fprintf(out, "next-in-flow=%p ", mNextInFlow); + } + + // Output the rect + out << mRect; + } + + // Output the children + if (nsnull != mFirstChild) { + if (PR_TRUE==outputMe) + { + if (0 != mState) { + fprintf(out, " [state=%08x]", mState); + } + fputs("<\n", out); + } + for (nsIFrame* child = mFirstChild; child; child->GetNextSibling(child)) { + child->List(out, aIndent + 1, aFilter); + } + if (PR_TRUE==outputMe) + { + for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out); + fputs(">\n", out); + } + } else { + if (PR_TRUE==outputMe) + { + if (0 != mState) { + fprintf(out, " [state=%08x]", mState); + } + fputs("<>\n", out); + } + } + + return NS_OK; +} + +#define VERIFY_ASSERT(_expr, _msg) \ + if (!(_expr)) { \ + DumpTree(); \ + } \ + NS_ASSERTION(_expr, _msg) + +NS_METHOD nsContainerFrame::VerifyTree() const +{ +#ifdef NS_DEBUG + NS_ASSERTION(0 == (mState & NS_FRAME_IN_REFLOW), "frame is in reflow"); + VERIFY_ASSERT(nsnull == mOverflowList, "bad overflow list"); +#endif + return NS_OK; +} + +PRInt32 nsContainerFrame::LengthOf(nsIFrame* aFrame) +{ + PRInt32 result = 0; + + while (nsnull != aFrame) { + result++; + aFrame->GetNextSibling(aFrame); + } + + return result; +} + +nsIFrame* nsContainerFrame::LastFrame(nsIFrame* aFrame) +{ + nsIFrame* lastChild = nsnull; + + while (nsnull != aFrame) { + lastChild = aFrame; + aFrame->GetNextSibling(aFrame); + } + + return lastChild; +} + +nsIFrame* nsContainerFrame::FrameAt(nsIFrame* aFrame, PRInt32 aIndex) +{ + while ((aIndex-- > 0) && (aFrame != nsnull)) { + aFrame->GetNextSibling(aFrame); + } + return aFrame; +} + +///////////////////////////////////////////////////////////////////////////// + +#ifdef NS_DEBUG + +PRBool nsContainerFrame::IsChild(const nsIFrame* aChild) const +{ + // Check the geometric parent + nsIFrame* parent; + + aChild->GetGeometricParent(parent); + if (parent != (nsIFrame*)this) { + return PR_FALSE; + } + + return PR_TRUE; +} + +void nsContainerFrame::DumpTree() const +{ + nsIFrame* root = (nsIFrame*)this; + nsIFrame* parent = mGeometricParent; + + while (nsnull != parent) { + root = parent; + parent->GetGeometricParent(parent); + } + + root->List(); +} + +#endif diff --git a/layout/html/base/src/nsContainerFrame.cpp b/layout/html/base/src/nsContainerFrame.cpp new file mode 100644 index 000000000000..f89b29ce1fdc --- /dev/null +++ b/layout/html/base/src/nsContainerFrame.cpp @@ -0,0 +1,700 @@ +/* -*- 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 "nsContainerFrame.h" +#include "nsIContent.h" +#include "nsIPresContext.h" +#include "nsIRenderingContext.h" +#include "nsIRunaround.h" +#include "nsISpaceManager.h" +#include "nsIStyleContext.h" +#include "nsRect.h" +#include "nsPoint.h" +#include "nsGUIEvent.h" +#include "nsStyleConsts.h" +#include "nsIView.h" +#include "nsVoidArray.h" +#include "nsISizeOfHandler.h" + +#ifdef NS_DEBUG +#undef NOISY +#else +#undef NOISY +#endif + +nsContainerFrame::nsContainerFrame(nsIContent* aContent, nsIFrame* aParent) + : nsSplittableFrame(aContent, aParent) +{ +} + +nsContainerFrame::~nsContainerFrame() +{ +} + +NS_IMETHODIMP +nsContainerFrame::SizeOf(nsISizeOfHandler* aHandler) const +{ + aHandler->Add(sizeof(*this)); + nsContainerFrame::SizeOfWithoutThis(aHandler); + return NS_OK; +} + +NS_IMETHODIMP +nsContainerFrame::Init(nsIPresContext& aPresContext, nsIFrame* aChildList) +{ + NS_PRECONDITION(nsnull == mFirstChild, "already initialized"); + + mFirstChild = aChildList; + return NS_OK; +} + +NS_IMETHODIMP +nsContainerFrame::DeleteFrame(nsIPresContext& aPresContext) +{ + // Delete our child frames before doing anything else. In particular + // we do all of this before our base class releases it's hold on the + // view. + for (nsIFrame* child = mFirstChild; child; ) { + mFirstChild = nsnull; // XXX hack until HandleEvent is not called until after destruction + nsIFrame* nextChild; + + child->GetNextSibling(nextChild); + child->DeleteFrame(aPresContext); + child = nextChild; + } + + nsFrame::DeleteFrame(aPresContext); + + return NS_OK; +} + +void +nsContainerFrame::SizeOfWithoutThis(nsISizeOfHandler* aHandler) const +{ + nsSplittableFrame::SizeOfWithoutThis(aHandler); + for (nsIFrame* child = mFirstChild; child; ) { + child->SizeOf(aHandler); + child->GetNextSibling(child); + } +} + +void +nsContainerFrame::PrepareContinuingFrame(nsIPresContext& aPresContext, + nsIFrame* aParent, + nsIStyleContext* aStyleContext, + nsContainerFrame* aContFrame) +{ + // Append the continuing frame to the flow + aContFrame->AppendToFlow(this); + aContFrame->SetStyleContext(&aPresContext, aStyleContext); +} + +NS_METHOD +nsContainerFrame::DidReflow(nsIPresContext& aPresContext, + nsDidReflowStatus aStatus) +{ + NS_FRAME_TRACE_MSG(NS_FRAME_TRACE_CALLS, + ("enter nsContainerFrame::DidReflow: status=%d", + aStatus)); + if (NS_FRAME_REFLOW_FINISHED == aStatus) { + nsIFrame* kid; + FirstChild(kid); + while (nsnull != kid) { + kid->DidReflow(aPresContext, aStatus); + kid->GetNextSibling(kid); + } + } + + NS_FRAME_TRACE_OUT("nsContainerFrame::DidReflow"); + + // Let nsFrame position and size our view (if we have one), and clear + // the NS_FRAME_IN_REFLOW bit + return nsFrame::DidReflow(aPresContext, aStatus); +} + +///////////////////////////////////////////////////////////////////////////// +// Child frame enumeration + +NS_METHOD nsContainerFrame::FirstChild(nsIFrame*& aFirstChild) const +{ + aFirstChild = mFirstChild; + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Painting + +NS_METHOD nsContainerFrame::Paint(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + PaintChildren(aPresContext, aRenderingContext, aDirtyRect); + return NS_OK; +} + +// aDirtyRect is in our coordinate system +// child rect's are also in our coordinate system +void nsContainerFrame::PaintChildren(nsIPresContext& aPresContext, + nsIRenderingContext& aRenderingContext, + const nsRect& aDirtyRect) +{ + // Set clip rect so that children don't leak out of us + const nsStyleDisplay* disp = + (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); + PRBool hidden = PR_FALSE; + if (NS_STYLE_OVERFLOW_HIDDEN == disp->mOverflow) { + aRenderingContext.PushState(); + aRenderingContext.SetClipRect(nsRect(0, 0, mRect.width, mRect.height), + nsClipCombine_kIntersect); + hidden = PR_TRUE; + } + + // XXX reminder: use the coordinates in the dirty rect to figure out + // which set of children are impacted and only do the intersection + // work for them. In addition, stop when we no longer overlap. + + nsIFrame* kid = mFirstChild; + while (nsnull != kid) { + nsIView *pView; + + kid->GetView(pView); + if (nsnull == pView) { + nsRect kidRect; + kid->GetRect(kidRect); + nsRect damageArea; +#ifdef NS_DEBUG + PRBool overlap = PR_FALSE; + if (nsIFrame::GetShowFrameBorders() && + ((kidRect.width == 0) || (kidRect.height == 0))) { + nscoord xmost = aDirtyRect.XMost(); + nscoord ymost = aDirtyRect.YMost(); + if ((aDirtyRect.x <= kidRect.x) && (kidRect.x < xmost) && + (aDirtyRect.y <= kidRect.y) && (kidRect.y < ymost)) { + overlap = PR_TRUE; + } + } + else { + overlap = damageArea.IntersectRect(aDirtyRect, kidRect); + } +#else + PRBool overlap = damageArea.IntersectRect(aDirtyRect, kidRect); +#endif + if (overlap) { + // Translate damage area into kid's coordinate system + nsRect kidDamageArea(damageArea.x - kidRect.x, + damageArea.y - kidRect.y, + damageArea.width, damageArea.height); + aRenderingContext.PushState(); + aRenderingContext.Translate(kidRect.x, kidRect.y); + kid->Paint(aPresContext, aRenderingContext, kidDamageArea); +#ifdef NS_DEBUG + if (nsIFrame::GetShowFrameBorders() && + (0 != kidRect.width) && (0 != kidRect.height)) { + nsIView* view; + GetView(view); + if (nsnull != view) { + aRenderingContext.SetColor(NS_RGB(0,0,255)); + } + else { + aRenderingContext.SetColor(NS_RGB(255,0,0)); + } + aRenderingContext.DrawRect(0, 0, kidRect.width, kidRect.height); + } +#endif + aRenderingContext.PopState(); + } + } + kid->GetNextSibling(kid); + } + + if (hidden) { + aRenderingContext.PopState(); + } +} + +///////////////////////////////////////////////////////////////////////////// +// Events + +NS_METHOD nsContainerFrame::HandleEvent(nsIPresContext& aPresContext, + nsGUIEvent* aEvent, + nsEventStatus& aEventStatus) +{ + aEventStatus = nsEventStatus_eIgnore; + + nsIFrame* kid; + FirstChild(kid); + while (nsnull != kid) { + nsRect kidRect; + kid->GetRect(kidRect); + if (kidRect.Contains(aEvent->point)) { + aEvent->point.MoveBy(-kidRect.x, -kidRect.y); + kid->HandleEvent(aPresContext, aEvent, aEventStatus); + aEvent->point.MoveBy(kidRect.x, kidRect.y); + break; + } + kid->GetNextSibling(kid); + } + + return NS_OK; +} + +NS_METHOD nsContainerFrame::GetCursorAndContentAt(nsIPresContext& aPresContext, + const nsPoint& aPoint, + nsIFrame** aFrame, + nsIContent** aContent, + PRInt32& aCursor) +{ + aCursor = NS_STYLE_CURSOR_INHERIT; + *aContent = mContent; + + nsIFrame* kid; + FirstChild(kid); + nsPoint tmp; + while (nsnull != kid) { + nsRect kidRect; + kid->GetRect(kidRect); + if (kidRect.Contains(aPoint)) { + tmp.MoveTo(aPoint.x - kidRect.x, aPoint.y - kidRect.y); + kid->GetCursorAndContentAt(aPresContext, tmp, aFrame, aContent, aCursor); + break; + } + kid->GetNextSibling(kid); + } + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Helper member functions + +/** + * Reflow a child frame and return the status of the reflow. If the + * child is complete and it has next-in-flows (it was a splittable child) + * then delete the next-in-flows. + */ +nsReflowStatus nsContainerFrame::ReflowChild(nsIFrame* aKidFrame, + nsIPresContext* aPresContext, + nsReflowMetrics& aDesiredSize, + const nsReflowState& aReflowState) +{ + nsReflowStatus status; + + NS_PRECONDITION(aReflowState.frame == aKidFrame, "bad reflow state"); +#ifdef NS_DEBUG + nsFrameState kidFrameState; + + aKidFrame->GetFrameState(kidFrameState); + NS_ASSERTION(kidFrameState & NS_FRAME_IN_REFLOW, "kid frame is not in reflow"); +#endif + aKidFrame->Reflow(*aPresContext, aDesiredSize, aReflowState, status); + + if (NS_FRAME_IS_COMPLETE(status)) { + nsIFrame* kidNextInFlow; + + aKidFrame->GetNextInFlow(kidNextInFlow); + if (nsnull != kidNextInFlow) { + // Remove all of the childs next-in-flows. Make sure that we ask + // the right parent to do the removal (it's possible that the + // parent is not this because we are executing pullup code) + nsIFrame* parent; + aKidFrame->GetGeometricParent(parent); + ((nsContainerFrame*)parent)->DeleteChildsNextInFlow(*aPresContext, aKidFrame); + } + } + return status; +} + +/** + * 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 + * + * @param aChild child this child's next-in-flow + * @return PR_TRUE if successful and PR_FALSE otherwise + */ +PRBool +nsContainerFrame::DeleteChildsNextInFlow(nsIPresContext& aPresContext, nsIFrame* aChild) +{ + NS_PRECONDITION(IsChild(aChild), "bad geometric parent"); + + nsIFrame* nextInFlow; + nsContainerFrame* parent; + + aChild->GetNextInFlow(nextInFlow); + NS_PRECONDITION(nsnull != nextInFlow, "null next-in-flow"); + nextInFlow->GetGeometricParent((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); + } + +#ifdef NS_DEBUG + PRInt32 childCount; + nsIFrame* firstChild; + + nextInFlow->FirstChild(firstChild); + childCount = LengthOf(firstChild); + + if ((0 != childCount) || (nsnull != firstChild)) { + nsIFrame* top = nextInFlow; + for (;;) { + nsIFrame* parent; + top->GetGeometricParent(parent); + if (nsnull == parent) { + break; + } + top = parent; + } + top->List(); + } + NS_ASSERTION((0 == childCount) && (nsnull == firstChild), + "deleting !empty next-in-flow"); +#endif + + // Disconnect the next-in-flow from the flow list + nextInFlow->BreakFromPrevFlow(); + + // Take the next-in-flow out of the parent's child list + if (parent->mFirstChild == nextInFlow) { + nextInFlow->GetNextSibling(parent->mFirstChild); + + } 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) + NS_ASSERTION(((nsContainerFrame*)parent)->IsChild(aChild), "screwy flow"); + aChild->GetNextSibling(nextSibling); + NS_ASSERTION(nextSibling == nextInFlow, "unexpected sibling"); + + nextInFlow->GetNextSibling(nextSibling); + aChild->SetNextSibling(nextSibling); + } + + // Delete the next-in-flow frame and adjust its parents child count + WillDeleteNextInFlowFrame(nextInFlow); + nextInFlow->DeleteFrame(aPresContext); + +#ifdef NS_DEBUG + aChild->GetNextInFlow(nextInFlow); + NS_POSTCONDITION(nsnull == nextInFlow, "non null next-in-flow"); +#endif + return PR_TRUE; +} + +void nsContainerFrame::WillDeleteNextInFlowFrame(nsIFrame* aNextInFlow) +{ +} + +/** + * Push aFromChild and its next siblings to the next-in-flow. Change the + * geometric parent of each frame that's pushed. If there is no next-in-flow + * the frames are placed on the overflow list (and the geometric parent is + * left unchanged). + * + * Updates the next-in-flow's child count. Does not update the + * pusher's child count. + * + * @param aFromChild the first child frame to push. It is disconnected from + * aPrevSibling + * @param aPrevSibling aFromChild's previous sibling. Must not be null. It's + * an error to push a parent's first child frame + */ +void nsContainerFrame::PushChildren(nsIFrame* aFromChild, nsIFrame* aPrevSibling) +{ + NS_PRECONDITION(nsnull != aFromChild, "null pointer"); + NS_PRECONDITION(nsnull != aPrevSibling, "pushing first child"); +#ifdef NS_DEBUG + nsIFrame* prevNextSibling; + + aPrevSibling->GetNextSibling(prevNextSibling); + NS_PRECONDITION(prevNextSibling == aFromChild, "bad prev sibling"); +#endif + + // Disconnect aFromChild from its previous sibling + aPrevSibling->SetNextSibling(nsnull); + + // Do we have a next-in-flow? + nsContainerFrame* nextInFlow = (nsContainerFrame*)mNextInFlow; + if (nsnull != nextInFlow) { + PRInt32 numChildren = 0; + nsIFrame* lastChild = nsnull; + + // Compute the number of children being pushed, and for each child change + // its geometric parent. Remember the last child + for (nsIFrame* f = aFromChild; nsnull != f; f->GetNextSibling(f)) { + numChildren++; +#ifdef NOISY + printf(" "); + ((nsFrame*)f)->ListTag(stdout); + printf("\n"); +#endif + lastChild = f; + f->SetGeometricParent(nextInFlow); + + nsIFrame* contentParent; + + f->GetContentParent(contentParent); + if (this == contentParent) { + f->SetContentParent(nextInFlow); + } + } + NS_ASSERTION(numChildren > 0, "no children to push"); + + // Prepend the frames to our next-in-flow's child list + lastChild->SetNextSibling(nextInFlow->mFirstChild); + nextInFlow->mFirstChild = aFromChild; + + } else { + // Add the frames to our overflow list + NS_ASSERTION(nsnull == mOverflowList, "bad overflow list"); +#ifdef NOISY + ListTag(stdout); + printf(": pushing kids to my overflow list\n"); +#endif + mOverflowList = aFromChild; + } +} + +/** + * Moves any frames on the overflwo lists (the prev-in-flow's overflow list and + * the receiver's overflow list) to the child list. + * + * Updates this frame's child count and content mapping. + * + * @return PR_TRUE if any frames were moved and PR_FALSE otherwise + */ +PRBool nsContainerFrame::MoveOverflowToChildList() +{ + PRBool result = PR_FALSE; + + // Check for an overflow list with our prev-in-flow + nsContainerFrame* prevInFlow = (nsContainerFrame*)mPrevInFlow; + if (nsnull != prevInFlow) { + if (nsnull != prevInFlow->mOverflowList) { + NS_ASSERTION(nsnull == mFirstChild, "bad overflow list"); + AppendChildren(prevInFlow->mOverflowList); + prevInFlow->mOverflowList = nsnull; + result = PR_TRUE; + } + } + + // It's also possible that we have an overflow list for ourselves + if (nsnull != mOverflowList) { + NS_ASSERTION(nsnull != mFirstChild, "overflow list but no mapped children"); + AppendChildren(mOverflowList, PR_FALSE); + mOverflowList = nsnull; + result = PR_TRUE; + } + + return result; +} + +/** + * Append child list starting at aChild to this frame's child list. Used for + * processing of the overflow list. + * + * Updates this frame's child count and content mapping. + * + * @param aChild the beginning of the child list + * @param aSetParent if true each child's geometric (and content parent if + * they're the same) parent is set to this frame. + */ +void nsContainerFrame::AppendChildren(nsIFrame* aChild, PRBool aSetParent) +{ + // Link the frames into our child frame list + if (nsnull == mFirstChild) { + // We have no children so aChild becomes the first child + mFirstChild = aChild; + } else { + nsIFrame* lastChild = LastFrame(mFirstChild); + lastChild->SetNextSibling(aChild); + } + + // Update our child count and last content offset + nsIFrame* lastChild; + for (nsIFrame* f = aChild; nsnull != f; f->GetNextSibling(f)) { + lastChild = f; + + // Reset the geometric parent if requested + if (aSetParent) { + nsIFrame* geometricParent; + nsIFrame* contentParent; + + f->GetGeometricParent(geometricParent); + f->GetContentParent(contentParent); + if (contentParent == geometricParent) { + f->SetContentParent(this); + } + f->SetGeometricParent(this); + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// Debugging + +NS_METHOD nsContainerFrame::List(FILE* out, PRInt32 aIndent, nsIListFilter *aFilter) const +{ + // if a filter is present, only output this frame if the filter says we should + nsIAtom* tag; + nsAutoString tagString; + mContent->GetTag(tag); + if (tag != nsnull) + tag->ToString(tagString); + PRBool outputMe = (PRBool)((nsnull==aFilter) || (PR_TRUE==aFilter->OutputTag(&tagString))); + if (PR_TRUE==outputMe) + { + // Indent + for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out); + + // Output the tag + ListTag(out); + nsIView* view; + GetView(view); + if (nsnull != view) { + fprintf(out, " [view=%p]", view); + } + + if (nsnull != mPrevInFlow) { + fprintf(out, "prev-in-flow=%p ", mPrevInFlow); + } + if (nsnull != mNextInFlow) { + fprintf(out, "next-in-flow=%p ", mNextInFlow); + } + + // Output the rect + out << mRect; + } + + // Output the children + if (nsnull != mFirstChild) { + if (PR_TRUE==outputMe) + { + if (0 != mState) { + fprintf(out, " [state=%08x]", mState); + } + fputs("<\n", out); + } + for (nsIFrame* child = mFirstChild; child; child->GetNextSibling(child)) { + child->List(out, aIndent + 1, aFilter); + } + if (PR_TRUE==outputMe) + { + for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out); + fputs(">\n", out); + } + } else { + if (PR_TRUE==outputMe) + { + if (0 != mState) { + fprintf(out, " [state=%08x]", mState); + } + fputs("<>\n", out); + } + } + + return NS_OK; +} + +#define VERIFY_ASSERT(_expr, _msg) \ + if (!(_expr)) { \ + DumpTree(); \ + } \ + NS_ASSERTION(_expr, _msg) + +NS_METHOD nsContainerFrame::VerifyTree() const +{ +#ifdef NS_DEBUG + NS_ASSERTION(0 == (mState & NS_FRAME_IN_REFLOW), "frame is in reflow"); + VERIFY_ASSERT(nsnull == mOverflowList, "bad overflow list"); +#endif + return NS_OK; +} + +PRInt32 nsContainerFrame::LengthOf(nsIFrame* aFrame) +{ + PRInt32 result = 0; + + while (nsnull != aFrame) { + result++; + aFrame->GetNextSibling(aFrame); + } + + return result; +} + +nsIFrame* nsContainerFrame::LastFrame(nsIFrame* aFrame) +{ + nsIFrame* lastChild = nsnull; + + while (nsnull != aFrame) { + lastChild = aFrame; + aFrame->GetNextSibling(aFrame); + } + + return lastChild; +} + +nsIFrame* nsContainerFrame::FrameAt(nsIFrame* aFrame, PRInt32 aIndex) +{ + while ((aIndex-- > 0) && (aFrame != nsnull)) { + aFrame->GetNextSibling(aFrame); + } + return aFrame; +} + +///////////////////////////////////////////////////////////////////////////// + +#ifdef NS_DEBUG + +PRBool nsContainerFrame::IsChild(const nsIFrame* aChild) const +{ + // Check the geometric parent + nsIFrame* parent; + + aChild->GetGeometricParent(parent); + if (parent != (nsIFrame*)this) { + return PR_FALSE; + } + + return PR_TRUE; +} + +void nsContainerFrame::DumpTree() const +{ + nsIFrame* root = (nsIFrame*)this; + nsIFrame* parent = mGeometricParent; + + while (nsnull != parent) { + root = parent; + parent->GetGeometricParent(parent); + } + + root->List(); +} + +#endif