/* -*- 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): * Pierre Phaneuf * * 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 "nsBox.h" #include "nsIPresContext.h" #include "nsCOMPtr.h" #include "nsIContent.h" #include "nsIViewManager.h" #include "nsIView.h" #include "nsIPresShell.h" #include "nsHTMLContainerFrame.h" #include "nsBoxToBlockAdaptor.h" #include "nsBoxLayoutState.h" #include "nsBoxFrame.h" #include "nsIPresContext.h" #include "nsCOMPtr.h" #include "nsUnitConversion.h" #include "nsHTMLAtoms.h" #include "nsXULAtoms.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 "nsContainerBox.h" nsContainerBox::nsContainerBox(nsIPresShell* aShell):nsBox(aShell) { mFirstChild = nsnull; mLastChild = nsnull; mChildCount = 0; mOrderBoxes = PR_FALSE; } nsContainerBox::~nsContainerBox() { } void nsContainerBox::Destroy(nsBoxLayoutState& aState) { SetLayoutManager(nsnull); ClearChildren(aState); } #ifdef DEBUG_LAYOUT void nsContainerBox::GetBoxName(nsAutoString& aName) { aName.Assign(NS_LITERAL_STRING("ContainerBox")); } #endif NS_IMETHODIMP nsContainerBox::GetChildBox(nsIBox** aBox) { *aBox = mFirstChild; return NS_OK; } PRInt32 nsContainerBox::GetChildCount() { return mChildCount; } PRInt32 nsContainerBox::CreateBoxList(nsBoxLayoutState& aState, nsIFrame* aFrameList, nsIBox*& aFirst, nsIBox*& aLast) { nsCOMPtr shell; aState.GetPresShell(getter_AddRefs(shell)); PRInt32 count = 0; if (aFrameList) { nsIBox* ibox = nsnull; if (NS_SUCCEEDED(aFrameList->QueryInterface(NS_GET_IID(nsIBox), (void**)&ibox)) && ibox) aFirst = ibox; else aFirst = new (shell) nsBoxToBlockAdaptor(shell, aFrameList); aFirst->SetParentBox(this); count++; aLast = aFirst; aFrameList = aFrameList->GetNextSibling(); nsIBox* last = aLast; while(aFrameList) { if (NS_SUCCEEDED(aFrameList->QueryInterface(NS_GET_IID(nsIBox), (void**)&ibox)) && ibox) aLast = ibox; else aLast = new (shell) nsBoxToBlockAdaptor(shell, aFrameList); aLast->SetParentBox(this); // check if this box is in a different ordinal group, and requires sorting PRUint32 ordinal; aLast->GetOrdinal(aState, ordinal); if (ordinal != DEFAULT_ORDINAL_GROUP) mOrderBoxes = PR_TRUE; last->SetNextBox(aLast); last = aLast; aFrameList = aFrameList->GetNextSibling(); count++; } } return count; } nsIBox* nsContainerBox::GetPrevious(nsIFrame* aFrame) { if (aFrame == nsnull) return nsnull; // find the frame to remove nsIBox* box = mFirstChild; nsIBox* prev = nsnull; while (box) { nsIFrame* frame = nsnull; box->GetFrame(&frame); if (frame == aFrame) { return prev; } prev = box; box->GetNextBox(&box); } return nsnull; } nsIBox* nsContainerBox::GetBox(nsIFrame* aFrame) { if (aFrame == nsnull) return nsnull; // recycle adaptors // nsIBox* ibox = nsnull; // if (NS_SUCCEEDED(aFrame->QueryInterface(NS_GET_IID(nsIBox), (void**)&ibox)) && ibox) // return ibox; // find the frame to remove nsIBox* box = mFirstChild; while (box) { nsIFrame* frame = nsnull; box->GetFrame(&frame); if (frame == aFrame) { return box; } box->GetNextBox(&box); } return nsnull; } nsIBox* nsContainerBox::GetBoxAt(PRInt32 aIndex) { // find the frame to remove nsIBox* child = mFirstChild; PRInt32 count = 0; while (child) { if (count == aIndex) { return child; } child->GetNextBox(&child); count++; } return nsnull; } void nsContainerBox::Remove(nsBoxLayoutState& aState, nsIFrame* aFrame) { // get the info before the frame nsIBox* prevBox = GetPrevious(aFrame); RemoveAfter(aState, prevBox); } void nsContainerBox::Insert(nsBoxLayoutState& aState, nsIFrame* aPrevFrame, nsIFrame* aFrameList) { nsIBox* prevBox = GetBox(aPrevFrame); NS_ASSERTION(aPrevFrame == nsnull || prevBox,"Error! The previous frame given is not in our list!"); // find the frame before this one // if no previous frame then we are inserting in front if (prevBox == nsnull) { // prepend them Prepend(aState, aFrameList); } else { // insert insert after previous info InsertAfter(aState, prevBox, aFrameList); } } void nsContainerBox::RemoveAfter(nsBoxLayoutState& aState, nsIBox* aPrevious) { nsIBox* toDelete = nsnull; if (aPrevious == nsnull) { NS_ASSERTION(mFirstChild,"Can't find first child"); toDelete = mFirstChild; if (mLastChild == mFirstChild) { nsIBox* next = nsnull; mFirstChild->GetNextBox(&next); mLastChild = next; } mFirstChild->GetNextBox(&mFirstChild); } else { aPrevious->GetNextBox(&toDelete); NS_ASSERTION(toDelete,"Can't find child to delete"); nsIBox* next = nsnull; toDelete->GetNextBox(&next); aPrevious->SetNextBox(next); if (mLastChild == toDelete) mLastChild = aPrevious; } // recycle adaptors nsIBoxToBlockAdaptor* adaptor = nsnull; if (NS_SUCCEEDED(toDelete->QueryInterface(NS_GET_IID(nsIBoxToBlockAdaptor), (void**)&adaptor)) && adaptor) { nsCOMPtr shell; aState.GetPresShell(getter_AddRefs(shell)); adaptor->Recycle(shell); } mChildCount--; if (mLayoutManager) mLayoutManager->ChildrenRemoved(this, aState, toDelete); } void nsContainerBox::ClearChildren(nsBoxLayoutState& aState) { if (mFirstChild && mLayoutManager) mLayoutManager->ChildrenRemoved(this, aState, mFirstChild); nsIBox* box = mFirstChild; while(box) { nsIBox* it = box; box->GetNextBox(&box); // recycle adaptors nsIBoxToBlockAdaptor* adaptor = nsnull; if (NS_SUCCEEDED(it->QueryInterface(NS_GET_IID(nsIBoxToBlockAdaptor), (void**)&adaptor)) && adaptor) { nsCOMPtr shell; aState.GetPresShell(getter_AddRefs(shell)); adaptor->Recycle(shell); } } mFirstChild= nsnull; mLastChild= nsnull; mChildCount = 0; } void nsContainerBox::Prepend(nsBoxLayoutState& aState, nsIFrame* aList) { nsIBox* first; nsIBox* last; mChildCount += CreateBoxList(aState, aList, first, last); if (!mFirstChild) mFirstChild= mLastChild= first; else { last->SetNextBox(mFirstChild); mFirstChild= first; } CheckBoxOrder(aState); if (mLayoutManager) mLayoutManager->ChildrenInserted(this, aState, nsnull, first); } void nsContainerBox::Append(nsBoxLayoutState& aState, nsIFrame* aList) { nsIBox* first; nsIBox* last; mChildCount += CreateBoxList(aState, aList, first, last); if (!mFirstChild) mFirstChild= first; else mLastChild->SetNextBox(first); mLastChild= last; CheckBoxOrder(aState); if (mLayoutManager) mLayoutManager->ChildrenAppended(this, aState, first); } void nsContainerBox::InsertAfter(nsBoxLayoutState& aState, nsIBox* aPrev, nsIFrame* aList) { nsIBox* first = nsnull; nsIBox* last = nsnull; mChildCount += CreateBoxList(aState, aList, first, last); nsIBox* next = nsnull; aPrev->GetNextBox(&next); last->SetNextBox(next); aPrev->SetNextBox(first); if (aPrev == mLastChild) mLastChild = last; CheckBoxOrder(aState); if (mLayoutManager) { mLayoutManager->ChildrenInserted(this, aState, aPrev, first); } } void nsContainerBox::InitChildren(nsBoxLayoutState& aState, nsIFrame* aList) { ClearChildren(aState); mChildCount += CreateBoxList(aState, aList, mFirstChild, mLastChild); CheckBoxOrder(aState); if (mLayoutManager) mLayoutManager->ChildrenSet(this, aState, mFirstChild); } void nsContainerBox::SetDebugOnChildList(nsBoxLayoutState& aState, nsIBox* aChild, PRBool aDebug) { nsIBox* child = nsnull; GetChildBox(&child); while (child) { child->SetDebug(aState, aDebug); child->GetNextBox(&child); } } void nsContainerBox::SanityCheck(nsFrameList& aFrameList) { #ifdef DEBUG // make sure the length match PRInt32 length = aFrameList.GetLength(); NS_ASSERTION(length == mChildCount,"nsBox::ERROR!! Box info list count does not match frame count!!"); // make sure last makes sense nsIBox* next = nsnull; NS_ASSERTION(mLastChild == nsnull || (NS_SUCCEEDED(mLastChild->GetNextBox(&next)) && next == nsnull),"nsBox::ERROR!! The last child is not really the last!!!"); nsIFrame* child = aFrameList.FirstChild(); nsIBox* box = mFirstChild; PRInt32 count = 0; while(box) { NS_ASSERTION(count <= mChildCount,"too many children!!!"); nsIFrame* frame = nsnull; nsIBox* parent = nsnull; NS_ASSERTION(NS_SUCCEEDED(box->GetFrame(&frame)) && frame == child,"nsBox::ERROR!! box info list and child info lists don't match!!!"); NS_ASSERTION(NS_SUCCEEDED(box->GetParentBox(&parent)) && parent == this,"nsBox::ERROR!! parent's don't match!!!"); box->GetNextBox(&box); child = child->GetNextSibling(); count++; } #endif } void nsContainerBox::CheckBoxOrder(nsBoxLayoutState& aState) { if (mOrderBoxes) { nsIBox** boxes = new nsIBox*[mChildCount]; // turn linked list into array for quick sort nsIBox* box = mFirstChild; nsIBox** boxPtr = boxes; while (box) { *boxPtr = box; box->GetNextBox(&box); ++boxPtr; } // sort the array by ordinal group, selection sort PRInt32 i, j, min; PRUint32 minOrd, jOrd; for(i = 0; i < mChildCount; i++) { min = i; boxes[min]->GetOrdinal(aState, minOrd); for(j = i + 1; j < mChildCount; j++) { boxes[j]->GetOrdinal(aState, jOrd); if (jOrd < minOrd) { min = j; minOrd = jOrd; } } box = boxes[min]; boxes[min] = boxes[i]; boxes[i] = box; } // turn the array back into linked list, with first and last cached mFirstChild = boxes[0]; mLastChild = boxes[mChildCount-1]; for (i = 0; i < mChildCount; ++i) { if (i <= mChildCount-2) boxes[i]->SetNextBox(boxes[i+1]); else boxes[i]->SetNextBox(nsnull); } delete [] boxes; } } NS_IMETHODIMP nsContainerBox::GetPrefSize(nsBoxLayoutState& aState, nsSize& aSize) { nsresult rv = NS_OK; aSize.width = 0; aSize.height = 0; PRBool collapsed = PR_FALSE; IsCollapsed(aState, collapsed); if (collapsed) return NS_OK; // if the size was not completely redefined in CSS then ask our children if (!nsIBox::AddCSSPrefSize(aState, this, aSize)) { aSize.width = 0; aSize.height = 0; if (mLayoutManager) { rv = mLayoutManager->GetPrefSize(this, aState, aSize); nsIBox::AddCSSPrefSize(aState, this, aSize); } else rv = nsBox::GetPrefSize(aState, aSize); } nsSize minSize(0,0); nsSize maxSize(0,0); GetMinSize(aState, minSize); GetMaxSize(aState, maxSize); BoundsCheck(minSize, aSize, maxSize); return rv; } NS_IMETHODIMP nsContainerBox::GetMinSize(nsBoxLayoutState& aState, nsSize& aSize) { nsresult rv = NS_OK; aSize.width = 0; aSize.height = 0; PRBool collapsed = PR_FALSE; IsCollapsed(aState, collapsed); if (collapsed) return NS_OK; // if the size was not completely redefined in CSS then ask our children if (!nsIBox::AddCSSMinSize(aState, this, aSize)) { aSize.width = 0; aSize.height = 0; if (mLayoutManager) { rv = mLayoutManager->GetMinSize(this, aState, aSize); nsIBox::AddCSSMinSize(aState, this, aSize); } else { rv = nsBox::GetMinSize(aState, aSize); } } return rv; } NS_IMETHODIMP nsContainerBox::GetMaxSize(nsBoxLayoutState& aState, nsSize& aSize) { nsresult rv = NS_OK; aSize.width = NS_INTRINSICSIZE; aSize.height = NS_INTRINSICSIZE; PRBool collapsed = PR_FALSE; IsCollapsed(aState, collapsed); if (collapsed) return NS_OK; // if the size was not completely redefined in CSS then ask our children if (!nsIBox::AddCSSMaxSize(aState, this, aSize)) { aSize.width = NS_INTRINSICSIZE; aSize.height = NS_INTRINSICSIZE; if (mLayoutManager) { rv = mLayoutManager->GetMaxSize(this, aState, aSize); nsIBox::AddCSSMaxSize(aState, this, aSize); } else { rv = nsBox::GetMaxSize(aState, aSize); } } return rv; } NS_IMETHODIMP nsContainerBox::GetAscent(nsBoxLayoutState& aState, nscoord& aAscent) { nsresult rv = NS_OK; aAscent = 0; PRBool collapsed = PR_FALSE; IsCollapsed(aState, collapsed); if (collapsed) return NS_OK; if (mLayoutManager) rv = mLayoutManager->GetAscent(this, aState, aAscent); else rv = nsBox::GetAscent(aState, aAscent); return rv; } NS_IMETHODIMP nsContainerBox::DoLayout(nsBoxLayoutState& aState) { PRUint32 oldFlags = 0; aState.GetLayoutFlags(oldFlags); aState.SetLayoutFlags(0); nsresult rv = NS_OK; if (mLayoutManager) rv = mLayoutManager->Layout(this, aState); aState.SetLayoutFlags(oldFlags); return rv; } NS_IMETHODIMP nsContainerBox::SetLayoutManager(nsIBoxLayout* aLayout) { mLayoutManager = aLayout; return NS_OK; } NS_IMETHODIMP nsContainerBox::GetLayoutManager(nsIBoxLayout** aLayout) { *aLayout = mLayoutManager; NS_IF_ADDREF(*aLayout); return NS_OK; } nsresult nsContainerBox::LayoutChildAt(nsBoxLayoutState& aState, nsIBox* aBox, const nsRect& aRect) { // get the current rect nsRect oldRect(0,0,0,0); aBox->GetBounds(oldRect); aBox->SetBounds(aState, aRect); PRBool dirty = PR_FALSE; PRBool dirtyChildren = PR_FALSE; aBox->IsDirty(dirty); aBox->HasDirtyChildren(dirtyChildren); PRBool layout = PR_TRUE; if (!(dirty || dirtyChildren) && aState.GetLayoutReason() != nsBoxLayoutState::Initial) layout = PR_FALSE; if (layout || (oldRect.width != aRect.width || oldRect.height != aRect.height)) { return aBox->Layout(aState); } return NS_OK; } NS_IMETHODIMP nsContainerBox::RelayoutChildAtOrdinal(nsBoxLayoutState& aState, nsIBox* aChild) { mOrderBoxes = PR_TRUE; PRUint32 ord; aChild->GetOrdinal(aState, ord); PRUint32 ordCmp; nsIBox* box = mFirstChild; nsIBox* newPrevSib = mFirstChild; box->GetOrdinal(aState, ordCmp); if (ord < ordCmp) { // new ordinal is lower than the lowest current ordinal, so it will not // have a previous sibling newPrevSib = nsnull; } else { // search for the box after which we will insert this box while (box) { box->GetOrdinal(aState, ordCmp); if (newPrevSib && ordCmp > ord) break; newPrevSib = box; box->GetNextBox(&box); } } // look for the previous sibling of |aChild| nsIBox* oldPrevSib = mFirstChild; while (oldPrevSib) { nsIBox* me; oldPrevSib->GetNextBox(&me); if (aChild == me) { break; } oldPrevSib = me; } // if we are moving |mFirstChild|, we'll have to update the |mFirstChild| // value later on PRBool firstChildMoved = PR_FALSE; if (aChild == mFirstChild) firstChildMoved = PR_TRUE; nsIBox* newNextSib; if (newPrevSib) { // insert |aChild| between |newPrevSib| and its next sibling newPrevSib->GetNextBox(&newNextSib); newPrevSib->SetNextBox(aChild); } else { // no |newPrevSib| found, so this box will become |mFirstChild| newNextSib = mFirstChild; mFirstChild = aChild; } // link up our new next sibling nsIBox* oldNextSib; aChild->GetNextBox(&oldNextSib); aChild->SetNextBox(newNextSib); // link |oldPrevSib| with |oldNextSib| to fill the gap left behind if (oldPrevSib) oldPrevSib->SetNextBox(oldNextSib); // if |newPrevSib| was the last child, then aChild is the new last child if (newPrevSib == mLastChild) mLastChild = aChild; if (firstChildMoved) mFirstChild = oldNextSib; return NS_OK; } NS_IMETHODIMP nsContainerBox::GetIndexOf(nsIBox* aBox, PRInt32* aIndex) { nsIBox* child = mFirstChild; PRInt32 count = 0; while (child) { if (aBox == child) { *aIndex = count; return NS_OK; } child->GetNextBox(&child); count++; } *aIndex = -1; return NS_OK; }