/* -*- 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.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.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * Pierre Phaneuf */ #include "nsCOMPtr.h" #include "nsVoidArray.h" #include "nsTableFrame.h" #include "nsTableBorderCollapser.h" #include "nsIRenderingContext.h" #include "nsIStyleContext.h" #include "nsStyleConsts.h" #include "nsIContent.h" #include "nsCellMap.h" #include "nsTableCellFrame.h" #include "nsHTMLParts.h" #include "nsTableColFrame.h" #include "nsTableColGroupFrame.h" #include "nsTableRowFrame.h" #include "nsTableRowGroupFrame.h" #include "nsTableOuterFrame.h" #include "nsIHTMLContent.h" #include "BasicTableLayoutStrategy.h" #include "FixedTableLayoutStrategy.h" #include "nsIPresContext.h" #include "nsCSSRendering.h" #include "nsStyleConsts.h" #include "nsVoidArray.h" #include "nsIView.h" #include "nsHTMLAtoms.h" #include "nsHTMLIIDs.h" #include "nsIReflowCommand.h" #include "nsLayoutAtoms.h" #include "nsIDeviceContext.h" #include "nsIStyleSet.h" #include "nsIPresShell.h" #include "nsIDOMElement.h" #include "nsIDOMHTMLElement.h" #include "nsIDOMHTMLBodyElement.h" #include "nsISizeOfHandler.h" #include "nsIScrollableFrame.h" #include "nsHTMLReflowCommand.h" static NS_DEFINE_IID(kIHTMLElementIID, NS_IDOMHTMLELEMENT_IID); static NS_DEFINE_IID(kIBodyElementIID, NS_IDOMHTMLBODYELEMENT_IID); static NS_DEFINE_IID(kITableRowGroupFrameIID, NS_ITABLEROWGROUPFRAME_IID); static NS_DEFINE_IID(kIScrollableFrameIID, NS_ISCROLLABLE_FRAME_IID); static const PRInt32 kColumnWidthIncrement=10; #if 1 PRBool nsDebugTable::gRflTableOuter = PR_FALSE; PRBool nsDebugTable::gRflTable = PR_FALSE; PRBool nsDebugTable::gRflRowGrp = PR_FALSE; PRBool nsDebugTable::gRflRow = PR_FALSE; PRBool nsDebugTable::gRflCell = PR_FALSE; PRBool nsDebugTable::gRflArea = PR_FALSE; #else PRBool nsDebugTable::gRflTableOuter = PR_TRUE; PRBool nsDebugTable::gRflTable = PR_TRUE; PRBool nsDebugTable::gRflRowGrp = PR_TRUE; PRBool nsDebugTable::gRflRow = PR_TRUE; PRBool nsDebugTable::gRflCell = PR_TRUE; PRBool nsDebugTable::gRflArea = PR_TRUE; #endif static PRInt32 gRflCount = 0; /* ----------- InnerTableReflowState ---------- */ struct InnerTableReflowState { // Our reflow state const nsHTMLReflowState& reflowState; // The body's available size (computed from the body's parent) nsSize availSize; // Flags for whether the max size is unconstrained PRBool unconstrainedWidth; PRBool unconstrainedHeight; // Running y-offset nscoord y; // Pointer to the footer in the table nsIFrame* footerFrame; // The first body section row group frame, i.e. not a header or footer nsIFrame* firstBodySection; // border/padding nsMargin mBorderPadding; InnerTableReflowState(nsIPresContext* aPresContext, const nsHTMLReflowState& aReflowState, const nsMargin& aBorderPadding) : reflowState(aReflowState), mBorderPadding(aBorderPadding) { y=0; // border/padding??? unconstrainedWidth = PRBool(aReflowState.availableWidth == NS_UNCONSTRAINEDSIZE); unconstrainedHeight = PRBool(aReflowState.availableHeight == NS_UNCONSTRAINEDSIZE); availSize.width = aReflowState.availableWidth; if (!unconstrainedWidth) { availSize.width -= aBorderPadding.left + aBorderPadding.right; } availSize.height = aReflowState.availableHeight; if (!unconstrainedHeight) { availSize.height -= aBorderPadding.top + aBorderPadding.bottom; } footerFrame = nsnull; firstBodySection = nsnull; } }; NS_IMETHODIMP nsTableFrame::GetFrameType(nsIAtom** aType) const { NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer"); *aType = nsLayoutAtoms::tableFrame; NS_ADDREF(*aType); return NS_OK; } /* --------------------- nsTableFrame -------------------- */ nsTableFrame::nsTableFrame() : nsHTMLContainerFrame(), mCellMap(nsnull), mTableLayoutStrategy(nsnull), mPercentBasisForRows(0) { mBits.mColumnWidthsSet = PR_FALSE; mBits.mColumnWidthsValid = PR_FALSE; mBits.mFirstPassValid = PR_FALSE; mBits.mIsInvariantWidth = PR_FALSE; // XXX We really shouldn't do this, but if we don't then we'll have a // problem with the tree control... #if 0 mColumnWidthsLength = 0; mColumnWidths = nsnull; #else mColumnWidthsLength = 5; mColumnWidths = new PRInt32[mColumnWidthsLength]; nsCRT::memset (mColumnWidths, 0, mColumnWidthsLength*sizeof(PRInt32)); #endif } NS_IMPL_ADDREF_INHERITED(nsTableFrame, nsHTMLContainerFrame) NS_IMPL_RELEASE_INHERITED(nsTableFrame, nsHTMLContainerFrame) nsresult nsTableFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr) { if (NULL == aInstancePtr) { return NS_ERROR_NULL_POINTER; } if (aIID.Equals(NS_GET_IID(nsITableLayout))) { // note there is no addref here, frames are not addref'd *aInstancePtr = (void*)(nsITableLayout*)this; return NS_OK; } else { return nsHTMLContainerFrame::QueryInterface(aIID, aInstancePtr); } } NS_IMETHODIMP nsTableFrame::Init(nsIPresContext* aPresContext, nsIContent* aContent, nsIFrame* aParent, nsIStyleContext* aContext, nsIFrame* aPrevInFlow) { nsresult rv; // Create the cell map // XXX Why do we do this for continuing frames? mCellMap = new nsTableCellMap(aPresContext, *this); // Let the base class do its processing rv = nsHTMLContainerFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow); if (aPrevInFlow) { // set my width, because all frames in a table flow are the same width and // code in nsTableOuterFrame depends on this being set nsSize size; aPrevInFlow->GetSize(size); mRect.width = size.width; } if ((NS_STYLE_BORDER_COLLAPSE == GetBorderCollapseStyle()) && !mBorderCollapser) { mBorderCollapser = new nsTableBorderCollapser(*this); // if new fails then we don't get collapsing borders } return rv; } nsTableFrame::~nsTableFrame() { if ((NS_STYLE_BORDER_COLLAPSE == GetBorderCollapseStyle()) && mBorderCollapser) { delete mBorderCollapser; } if (nsnull!=mCellMap) { delete mCellMap; mCellMap = nsnull; } if (nsnull!=mColumnWidths) { delete [] mColumnWidths; mColumnWidths = nsnull; } if (nsnull!=mTableLayoutStrategy) { delete mTableLayoutStrategy; mTableLayoutStrategy = nsnull; } } NS_IMETHODIMP nsTableFrame::Destroy(nsIPresContext* aPresContext) { mColGroups.DestroyFrames(aPresContext); return nsHTMLContainerFrame::Destroy(aPresContext); } // Helper function. It marks the table frame as dirty and generates // a reflow command nsresult nsTableFrame::AddTableDirtyReflowCommand(nsIPresContext* aPresContext, nsIFrame* aTableFrame) { nsFrameState frameState; nsIFrame* tableParentFrame; nsIReflowCommand* reflowCmd; nsresult rv; nsIPresShell* presShell; aPresContext->GetShell(&presShell); // Mark the table frame as dirty aTableFrame->GetFrameState(&frameState); frameState |= NS_FRAME_IS_DIRTY; aTableFrame->SetFrameState(frameState); // Target the reflow comamnd at its parent frame aTableFrame->GetParent(&tableParentFrame); rv = NS_NewHTMLReflowCommand(&reflowCmd, tableParentFrame, nsIReflowCommand::ReflowDirty); if (NS_SUCCEEDED(rv)) { // Add the reflow command rv = presShell->AppendReflowCommand(reflowCmd); NS_RELEASE(reflowCmd); } NS_RELEASE(presShell); return rv; } // XXX this needs to be cleaned up so that the frame constructor breaks out col group // frames into a separate child list. NS_IMETHODIMP nsTableFrame::SetInitialChildList(nsIPresContext* aPresContext, nsIAtom* aListName, nsIFrame* aChildList) { nsresult rv=NS_OK; // I know now that I have all my children, so build the cell map nsIFrame *childFrame = aChildList; nsIFrame *prevMainChild = nsnull; nsIFrame *prevColGroupChild = nsnull; for ( ; nsnull!=childFrame; ) { const nsStyleDisplay *childDisplay; childFrame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct *&)childDisplay); // XXX this if should go away if (PR_TRUE==IsRowGroup(childDisplay->mDisplay)) { if (mFrames.IsEmpty()) mFrames.SetFrames(childFrame); else prevMainChild->SetNextSibling(childFrame); prevMainChild = childFrame; } else if (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == childDisplay->mDisplay) { if (mColGroups.IsEmpty()) mColGroups.SetFrames(childFrame); else prevColGroupChild->SetNextSibling(childFrame); prevColGroupChild = childFrame; } else { // unknown frames go on the main list for now if (mFrames.IsEmpty()) mFrames.SetFrames(childFrame); else prevMainChild->SetNextSibling(childFrame); prevMainChild = childFrame; } nsIFrame *prevChild = childFrame; childFrame->GetNextSibling(&childFrame); prevChild->SetNextSibling(nsnull); } if (nsnull!=prevMainChild) prevMainChild->SetNextSibling(nsnull); if (nsnull!=prevColGroupChild) prevColGroupChild->SetNextSibling(nsnull); // If we have a prev-in-flow, then we're a table that has been split and // so don't treat this like an append if (!mPrevInFlow) { // process col groups first so that real cols get constructed before // anonymous ones due to cells in rows. InsertColGroups(*aPresContext, 0, mColGroups.FirstChild()); AppendRowGroups(*aPresContext, mFrames.FirstChild()); } if (HasGroupRules()) { ProcessGroupRules(aPresContext); } return rv; } NS_IMETHODIMP nsTableFrame::IsPercentageBase(PRBool& aBase) const { aBase = PR_TRUE; return NS_OK; } void nsTableFrame::AttributeChangedFor(nsIPresContext* aPresContext, nsIFrame* aFrame, nsIContent* aContent, nsIAtom* aAttribute) { nsIAtom* frameType; aFrame->GetFrameType(&frameType); if (nsLayoutAtoms::tableCellFrame == frameType) { if ((nsHTMLAtoms::rowspan == aAttribute) || (nsHTMLAtoms::colspan == aAttribute)) { nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { // for now just remove the cell from the map and reinsert it nsTableCellFrame* cellFrame = (nsTableCellFrame*)aFrame; PRInt32 rowIndex, colIndex; cellFrame->GetRowIndex(rowIndex); cellFrame->GetColIndex(colIndex); RemoveCell(*aPresContext, cellFrame, rowIndex); nsVoidArray cells; cells.AppendElement(cellFrame); InsertCells(*aPresContext, cells, rowIndex, colIndex - 1); // invalidate the column widths and generate a reflow command InvalidateColumnWidths(); AddTableDirtyReflowCommand(aPresContext, this); } } } NS_IF_RELEASE(frameType); } /* ****** CellMap methods ******* */ /* counts columns in column groups */ PRInt32 nsTableFrame::GetSpecifiedColumnCount () { PRInt32 colCount = 0; nsIFrame * childFrame = mColGroups.FirstChild(); while (nsnull!=childFrame) { colCount += ((nsTableColGroupFrame *)childFrame)->GetColCount(); childFrame->GetNextSibling(&childFrame); } return colCount; } PRInt32 nsTableFrame::GetRowCount () const { PRInt32 rowCount = 0; nsTableCellMap *cellMap = GetCellMap(); NS_ASSERTION(nsnull!=cellMap, "GetRowCount null cellmap"); if (nsnull!=cellMap) rowCount = cellMap->GetRowCount(); return rowCount; } /* return the effective col count */ PRInt32 nsTableFrame::GetColCount () { PRInt32 colCount = 0; nsTableCellMap* cellMap = GetCellMap(); NS_ASSERTION(nsnull != cellMap, "GetColCount null cellmap"); if (nsnull != cellMap) colCount = cellMap->GetColCount(); return colCount; } nsTableColFrame* nsTableFrame::GetColFrame(PRInt32 aColIndex) { return (nsTableColFrame *)mColFrames.ElementAt(aColIndex); } // can return nsnull nsTableCellFrame* nsTableFrame::GetCellFrameAt(PRInt32 aRowIndex, PRInt32 aColIndex) { nsTableCellMap* cellMap = GetCellMap(); if (cellMap) return cellMap->GetCellInfoAt(aRowIndex, aColIndex); return nsnull; } PRInt32 nsTableFrame::GetEffectiveRowSpan(PRInt32 aRowIndex, const nsTableCellFrame& aCell) const { nsTableCellMap* cellMap = GetCellMap(); NS_PRECONDITION (nsnull != cellMap, "bad call, cellMap not yet allocated."); PRInt32 colIndex; aCell.GetColIndex(colIndex); return cellMap->GetEffectiveRowSpan(aRowIndex, colIndex); } PRInt32 nsTableFrame::GetEffectiveRowSpan(const nsTableCellFrame& aCell) const { nsTableCellMap* cellMap = GetCellMap(); NS_PRECONDITION (nsnull != cellMap, "bad call, cellMap not yet allocated."); PRInt32 colIndex, rowIndex; aCell.GetColIndex(colIndex); aCell.GetRowIndex(rowIndex); return cellMap->GetEffectiveRowSpan(rowIndex, colIndex); } PRInt32 nsTableFrame::GetEffectiveColSpan(const nsTableCellFrame& aCell) const { nsTableCellMap* cellMap = GetCellMap(); NS_PRECONDITION (nsnull != cellMap, "bad call, cellMap not yet allocated."); PRInt32 colIndex, rowIndex; aCell.GetColIndex(colIndex); aCell.GetRowIndex(rowIndex); return cellMap->GetEffectiveColSpan(rowIndex, colIndex); } PRInt32 nsTableFrame::GetEffectiveCOLSAttribute() { nsTableCellMap *cellMap = GetCellMap(); NS_PRECONDITION (nsnull!=cellMap, "null cellMap."); PRInt32 result; const nsStyleTable *tableStyle=nsnull; GetStyleData(eStyleStruct_Table, (const nsStyleStruct *&)tableStyle); result = tableStyle->mCols; PRInt32 numCols = GetColCount(); if (result>numCols) result = numCols; return result; } PRBool nsTableFrame::HasGroupRules() const { const nsStyleTable* tableStyle = nsnull; GetStyleData(eStyleStruct_Table, (const nsStyleStruct *&)tableStyle); if (NS_STYLE_TABLE_RULES_GROUPS == tableStyle->mRules) { return PR_TRUE; } return PR_FALSE; } // this won't work until bug 12948 is resolved and col groups are considered void nsTableFrame::ProcessGroupRules(nsIPresContext* aPresContext) { PRInt32 numCols = GetColCount(); // process row groups nsIFrame* iFrame; for (iFrame = mFrames.FirstChild(); iFrame; iFrame->GetNextSibling(&iFrame)) { nsIAtom* frameType; iFrame->GetFrameType(&frameType); if (nsLayoutAtoms::tableRowGroupFrame == frameType) { nsTableRowGroupFrame* rgFrame = (nsTableRowGroupFrame *)iFrame; PRInt32 startRow = rgFrame->GetStartRowIndex(); PRInt32 numGroupRows; rgFrame->GetRowCount(numGroupRows, PR_FALSE); PRInt32 endRow = startRow + numGroupRows - 1; if (startRow == endRow) { continue; } for (PRInt32 rowX = startRow; rowX <= endRow; rowX++) { for (PRInt32 colX = 0; colX < numCols; colX++) { PRBool originates; nsTableCellFrame* cell = GetCellInfoAt(rowX, colX, &originates); if (originates) { nsCOMPtr styleContext; cell->GetStyleContext(getter_AddRefs(styleContext)); nsStyleSpacing* spacing = (nsStyleSpacing*)styleContext->GetMutableStyleData(eStyleStruct_Spacing); if (rowX == startRow) { spacing->SetBorderStyle(NS_SIDE_BOTTOM, NS_STYLE_BORDER_STYLE_NONE); } else if (rowX == endRow) { spacing->SetBorderStyle(NS_SIDE_TOP, NS_STYLE_BORDER_STYLE_NONE); } else { spacing->SetBorderStyle(NS_SIDE_TOP, NS_STYLE_BORDER_STYLE_NONE); spacing->SetBorderStyle(NS_SIDE_BOTTOM, NS_STYLE_BORDER_STYLE_NONE); } styleContext->RecalcAutomaticData(aPresContext); } } } } NS_IF_RELEASE(frameType); } } void nsTableFrame::AdjustRowIndices(nsIPresContext* aPresContext, PRInt32 aRowIndex, PRInt32 aAdjustment) { // Iterate over the row groups and adjust the row indices of all rows // whose index is >= aRowIndex. nsIFrame* rowGroupFrame = mFrames.FirstChild(); for ( ; rowGroupFrame; rowGroupFrame->GetNextSibling(&rowGroupFrame)) { const nsStyleDisplay *rowGroupDisplay; rowGroupFrame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct *&)rowGroupDisplay); if (IsRowGroup(rowGroupDisplay->mDisplay)) { AdjustRowIndices(aPresContext, rowGroupFrame, aRowIndex, aAdjustment); } } } NS_IMETHODIMP nsTableFrame::AdjustRowIndices(nsIPresContext* aPresContext, nsIFrame* aRowGroup, PRInt32 aRowIndex, PRInt32 anAdjustment) { nsresult rv = NS_OK; nsIFrame *rowFrame; aRowGroup->FirstChild(aPresContext, nsnull, &rowFrame); for ( ; nsnull!=rowFrame; rowFrame->GetNextSibling(&rowFrame)) { const nsStyleDisplay *rowDisplay; rowFrame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct *&)rowDisplay); if (NS_STYLE_DISPLAY_TABLE_ROW==rowDisplay->mDisplay) { PRInt32 index = ((nsTableRowFrame*)rowFrame)->GetRowIndex(); if (index >= aRowIndex) ((nsTableRowFrame *)rowFrame)->SetRowIndex(index+anAdjustment); } else if (NS_STYLE_DISPLAY_TABLE_ROW_GROUP==rowDisplay->mDisplay) { AdjustRowIndices(aPresContext, rowFrame, aRowIndex, anAdjustment); } } return rv; } void nsTableFrame::InsertColGroups(nsIPresContext& aPresContext, PRInt32 aStartColIndex, nsIFrame* aFirstFrame, nsIFrame* aLastFrame) { PRInt32 colIndex = aStartColIndex; nsTableColGroupFrame* firstColGroupToReset = nsnull; nsIFrame* kidFrame = aFirstFrame; PRBool didLastFrame = PR_FALSE; while (kidFrame) { nsIAtom* kidType; kidFrame->GetFrameType(&kidType); if (nsLayoutAtoms::tableColGroupFrame == kidType) { if (didLastFrame) { firstColGroupToReset = (nsTableColGroupFrame*)kidFrame; break; } else { nsTableColGroupFrame* cgFrame = (nsTableColGroupFrame*)kidFrame; cgFrame->SetStartColumnIndex(colIndex); nsIFrame* firstCol; kidFrame->FirstChild(&aPresContext, nsnull, &firstCol); cgFrame->AddColsToTable(aPresContext, colIndex, PR_FALSE, firstCol); PRInt32 numCols = cgFrame->GetColCount(); colIndex += numCols; } } NS_IF_RELEASE(kidType); if (kidFrame == aLastFrame) { didLastFrame = PR_TRUE; } kidFrame->GetNextSibling(&kidFrame); } if (firstColGroupToReset) { nsTableColGroupFrame::ResetColIndices(&aPresContext, firstColGroupToReset, aStartColIndex); } } void nsTableFrame::InsertCol(nsIPresContext& aPresContext, nsTableColFrame& aColFrame, PRInt32 aColIndex) { mColFrames.InsertElementAt(&aColFrame, aColIndex); nsTableColType insertedColType = aColFrame.GetType(); PRInt32 numCacheCols = mColFrames.Count(); nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { PRInt32 numMapCols = cellMap->GetColCount(); if (numCacheCols > numMapCols) { PRBool removedFromCache = PR_FALSE; if (eColAnonymousCell != insertedColType) { nsTableColFrame* lastCol = (nsTableColFrame *)mColFrames.ElementAt(numCacheCols - 1); if (lastCol) { nsTableColType lastColType = lastCol->GetType(); if (eColAnonymousCell == lastColType) { // remove the col from the cache mColFrames.RemoveElementAt(numCacheCols - 1); // remove the col from the eColGroupAnonymousCell col group nsTableColGroupFrame* lastColGroup = (nsTableColGroupFrame *)mColGroups.LastChild(); if (lastColGroup) { lastColGroup->RemoveChild(aPresContext, *lastCol, PR_FALSE); } // remove the col group if it is empty if (lastColGroup->GetColCount() <= 0) { mColGroups.DestroyFrame(&aPresContext, (nsIFrame*)lastColGroup); } removedFromCache = PR_TRUE; } } } if (!removedFromCache) { cellMap->AddColsAtEnd(1); } } } } void nsTableFrame::RemoveCol(nsIPresContext& aPresContext, nsTableColGroupFrame* aColGroupFrame, PRInt32 aColIndex, PRBool aRemoveFromCache, PRBool aRemoveFromCellMap) { if (aRemoveFromCache) { mColFrames.RemoveElementAt(aColIndex); } if (aRemoveFromCellMap) { nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { // check to see if the cell map can remove the col if (cellMap->RemoveUnusedCols(1) < 1) { // it couldn't be removed so we need a new anonymous col frame CreateAnonymousColFrames(aPresContext, 1, eColAnonymousCell, PR_TRUE); } } } } /** Get the cell map for this table frame. It is not always mCellMap. * Only the firstInFlow has a legit cell map */ nsTableCellMap * nsTableFrame::GetCellMap() const { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); if (this!=firstInFlow) { return firstInFlow->GetCellMap(); } return mCellMap; } // XXX this needs to be moved to nsCSSFrameConstructor nsTableColGroupFrame* nsTableFrame::CreateAnonymousColGroupFrame(nsIPresContext& aPresContext, nsTableColGroupType aColGroupType) { nsCOMPtr colGroupContent; GetContent(getter_AddRefs(colGroupContent)); nsCOMPtr colGroupStyle; aPresContext.ResolvePseudoStyleContextFor(colGroupContent, nsHTMLAtoms::tableColGroupPseudo, mStyleContext, PR_FALSE, getter_AddRefs(colGroupStyle)); // Create a col group frame nsIFrame* newFrame; nsCOMPtr presShell; aPresContext.GetShell(getter_AddRefs(presShell)); nsresult result = NS_NewTableColGroupFrame(presShell, &newFrame); if (NS_SUCCEEDED(result) && newFrame) { ((nsTableColGroupFrame *)newFrame)->SetType(aColGroupType); newFrame->Init(&aPresContext, colGroupContent, this, colGroupStyle, nsnull); } return (nsTableColGroupFrame *)newFrame; } void nsTableFrame::CreateAnonymousColFrames(nsIPresContext& aPresContext, PRInt32 aNumColsToAdd, nsTableColType aColType, PRBool aDoAppend, nsIFrame* aPrevColIn) { // get the last col group frame nsTableColGroupFrame* colGroupFrame = nsnull; nsIFrame* childFrame = mColGroups.FirstChild(); while (childFrame) { nsIAtom* frameType = nsnull; childFrame->GetFrameType(&frameType); if (nsLayoutAtoms::tableColGroupFrame == frameType) { colGroupFrame = (nsTableColGroupFrame *)childFrame; } childFrame->GetNextSibling(&childFrame); NS_IF_RELEASE(frameType); } nsTableColGroupType lastColGroupType = eColGroupContent; nsTableColGroupType newColGroupType = eColGroupContent; if (colGroupFrame) { lastColGroupType = colGroupFrame->GetType(); } if (eColAnonymousCell == aColType) { if (eColGroupAnonymousCell != lastColGroupType) { newColGroupType = eColGroupAnonymousCell; } } else if (eColAnonymousCol == aColType) { if (eColGroupAnonymousCol != lastColGroupType) { newColGroupType = eColGroupAnonymousCol; } } else { NS_ASSERTION(PR_FALSE, "CreateAnonymousColFrames called incorrectly"); return; } if (eColGroupContent != newColGroupType) { PRInt32 colIndex = (colGroupFrame) ? colGroupFrame->GetStartColumnIndex() + colGroupFrame->GetColCount() : 0; colGroupFrame = CreateAnonymousColGroupFrame(aPresContext, newColGroupType); if (!colGroupFrame) { return; } mColGroups.AppendFrame(this, colGroupFrame); // add the new frame to the child list colGroupFrame->SetStartColumnIndex(colIndex); } nsIFrame* prevCol = (aDoAppend) ? colGroupFrame->GetChildList().LastChild() : aPrevColIn; nsIFrame* firstNewFrame; CreateAnonymousColFrames(aPresContext, *colGroupFrame, aNumColsToAdd, aColType, PR_TRUE, prevCol, &firstNewFrame); } // XXX this needs to be moved to nsCSSFrameConstructor // Right now it only creates the col frames at the end void nsTableFrame::CreateAnonymousColFrames(nsIPresContext& aPresContext, nsTableColGroupFrame& aColGroupFrame, PRInt32 aNumColsToAdd, nsTableColType aColType, PRBool aAddToColGroupAndTable, nsIFrame* aPrevFrameIn, nsIFrame** aFirstNewFrame) { *aFirstNewFrame = nsnull; nsIFrame* lastColFrame = nsnull; nsIFrame* childFrame; // Get the last col frame aColGroupFrame.FirstChild(&aPresContext, nsnull, &childFrame); while (childFrame) { nsIAtom* frameType = nsnull; childFrame->GetFrameType(&frameType); if (nsLayoutAtoms::tableColFrame == frameType) { lastColFrame = (nsTableColGroupFrame *)childFrame; } NS_IF_RELEASE(frameType); childFrame->GetNextSibling(&childFrame); } PRInt32 startIndex = mColFrames.Count(); PRInt32 lastIndex = startIndex + aNumColsToAdd - 1; for (PRInt32 childX = startIndex; childX <= lastIndex; childX++) { nsCOMPtr iContent; nsCOMPtr styleContext; if ((aColType == eColAnonymousCol) && aPrevFrameIn) { // a col due to a span in a previous col uses the style context of the col aPrevFrameIn->GetStyleContext(getter_AddRefs(styleContext)); } else { // all other anonymous cols use a pseudo style context of the col group aColGroupFrame.GetContent(getter_AddRefs(iContent)); aPresContext.ResolvePseudoStyleContextFor(iContent, nsHTMLAtoms::tableColPseudo, styleContext, PR_FALSE, getter_AddRefs(styleContext)); } // create the new col frame nsIFrame* colFrame; nsCOMPtr presShell; aPresContext.GetShell(getter_AddRefs(presShell)); NS_NewTableColFrame(presShell, &colFrame); ((nsTableColFrame *) colFrame)->SetType(aColType); colFrame->Init(&aPresContext, iContent, &aColGroupFrame, styleContext, nsnull); colFrame->SetInitialChildList(&aPresContext, nsnull, nsnull); ((nsTableColFrame *)colFrame)->SetType(aColType); // Add the col to the sibling chain if (lastColFrame) { lastColFrame->SetNextSibling(colFrame); } lastColFrame = colFrame; if (childX == startIndex) { *aFirstNewFrame = colFrame; } } if (aAddToColGroupAndTable) { nsFrameList& cols = aColGroupFrame.GetChildList(); // the chain already exists, now add it to the col group child list if (!aPrevFrameIn) { cols.AppendFrames(&aColGroupFrame, *aFirstNewFrame); } // get the starting col index in the cache PRInt32 startColIndex = aColGroupFrame.GetStartColumnIndex(); if (aPrevFrameIn) { nsTableColFrame* colFrame = (nsTableColFrame*)nsTableFrame::GetFrameAtOrBefore(&aPresContext, (nsIFrame*)&aColGroupFrame, aPrevFrameIn, nsLayoutAtoms::tableColFrame); if (colFrame) { startColIndex = colFrame->GetColIndex(); } } aColGroupFrame.AddColsToTable(aPresContext, startColIndex, PR_TRUE, *aFirstNewFrame, lastColFrame); } } PRInt32 nsTableFrame::AppendCell(nsIPresContext& aPresContext, nsTableCellFrame& aCellFrame, PRInt32 aRowIndex) { PRInt32 colIndex = 0; nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { colIndex = cellMap->AppendCell(aCellFrame, aRowIndex, PR_TRUE); PRInt32 numColsInMap = GetColCount(); PRInt32 numColsInCache = mColFrames.Count(); PRInt32 numColsToAdd = numColsInMap - numColsInCache; if (numColsToAdd > 0) { // this sets the child list, updates the col cache and cell map CreateAnonymousColFrames(aPresContext, numColsToAdd, eColAnonymousCell, PR_TRUE); } } return colIndex; } void nsTableFrame::InsertCells(nsIPresContext& aPresContext, nsVoidArray& aCellFrames, PRInt32 aRowIndex, PRInt32 aColIndexBefore) { nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { cellMap->InsertCells(aCellFrames, aRowIndex, aColIndexBefore); PRInt32 numColsInMap = GetColCount(); PRInt32 numColsInCache = mColFrames.Count(); PRInt32 numColsToAdd = numColsInMap - numColsInCache; if (numColsToAdd > 0) { // this sets the child list, updates the col cache and cell map CreateAnonymousColFrames(aPresContext, numColsToAdd, eColAnonymousCell, PR_TRUE); } } } // this removes the frames from the col group and table, but not the cell map PRInt32 nsTableFrame::DestroyAnonymousColFrames(nsIPresContext& aPresContext, PRInt32 aNumFrames) { // only remove cols that are of type eTypeAnonymous cell (they are at the end) PRInt32 endIndex = mColFrames.Count() - 1; PRInt32 startIndex = (endIndex - aNumFrames) + 1; PRInt32 numColsRemoved = 0; for (PRInt32 colX = endIndex; colX >= startIndex; colX--) { nsTableColFrame* colFrame = GetColFrame(colX); if (colFrame && (eColAnonymousCell == colFrame->GetType())) { nsTableColGroupFrame* cgFrame; colFrame->GetParent((nsIFrame**)&cgFrame); // remove the frame from the colgroup cgFrame->RemoveChild(aPresContext, *colFrame, PR_FALSE); // remove the frame from the cache, but not the cell map RemoveCol(aPresContext, nsnull, colX, PR_TRUE, PR_FALSE); numColsRemoved++; } else { break; } } return (aNumFrames - numColsRemoved); } void nsTableFrame::RemoveCell(nsIPresContext& aPresContext, nsTableCellFrame* aCellFrame, PRInt32 aRowIndex) { nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { cellMap->RemoveCell(aCellFrame, aRowIndex); PRInt32 numColsInMap = GetColCount(); // cell map's notion of num cols PRInt32 numColsInCache = mColFrames.Count(); PRInt32 numColsNotRemoved = DestroyAnonymousColFrames(aPresContext, numColsInCache - numColsInMap); // if the cell map has fewer cols than the cache, correct it if (numColsNotRemoved > 0) { cellMap->AddColsAtEnd(numColsNotRemoved); } } } // this cannot extend beyond a single row group void nsTableFrame::AppendRows(nsIPresContext& aPresContext, nsTableRowGroupFrame& aRowGroupFrame, nsVoidArray& aRowFrames) { nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { PRInt32 rowIndex = cellMap->GetRowCount(); InsertRows(aPresContext, aRowGroupFrame, aRowFrames, rowIndex, PR_TRUE); } } PRInt32 nsTableFrame::InsertRow(nsIPresContext& aPresContext, nsTableRowGroupFrame& aRowGroupFrame, nsIFrame& aRowFrame, PRInt32 aRowIndex, PRBool aConsiderSpans) { nsVoidArray rows; rows.AppendElement(&aRowFrame); return InsertRows(aPresContext, aRowGroupFrame, rows, aRowIndex, aConsiderSpans); } // this cannot extend beyond a single row group PRInt32 nsTableFrame::InsertRows(nsIPresContext& aPresContext, nsTableRowGroupFrame& aRowGroupFrame, nsVoidArray& aRowFrames, PRInt32 aRowIndex, PRBool aConsiderSpans) { //printf("insertRowsBefore firstRow=%d \n", aRowIndex); //Dump(PR_TRUE, PR_FALSE, PR_TRUE); PRInt32 numColsToAdd = 0; nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { PRInt32 origNumRows = cellMap->GetRowCount(); PRInt32 numNewRows = aRowFrames.Count(); cellMap->InsertRows(&aPresContext, aRowGroupFrame, aRowFrames, aRowIndex, aConsiderSpans); PRInt32 numColsInMap = GetColCount(); // cell map's notion of num cols PRInt32 numColsInCache = mColFrames.Count(); numColsToAdd = numColsInMap - numColsInCache; if (numColsToAdd > 0) { // this sets the child list, updates the col cache and cell map CreateAnonymousColFrames(aPresContext, numColsToAdd, eColAnonymousCell, PR_TRUE); } if (aRowIndex < origNumRows) { AdjustRowIndices(&aPresContext, aRowIndex, numNewRows); } // assign the correct row indices to the new rows. If they were adjusted above // it may not have been done correctly because each row is constructed with index 0 for (PRInt32 rowX = 0; rowX < numNewRows; rowX++) { nsTableRowFrame* rowFrame = (nsTableRowFrame *) aRowFrames.ElementAt(rowX); rowFrame->SetRowIndex(aRowIndex + rowX); } } //printf("insertRowsAfter \n"); //Dump(PR_TRUE, PR_FALSE, PR_TRUE); return numColsToAdd; } // this cannot extend beyond a single row group void nsTableFrame::RemoveRows(nsIPresContext& aPresContext, PRInt32 aFirstRowIndex, PRInt32 aNumRowsToRemove, PRBool aConsiderSpans) { //printf("removeRowsBefore firstRow=%d numRows=%d\n", aFirstRowIndex, aNumRowsToRemove); //Dump(PR_TRUE, PR_FALSE, PR_TRUE); nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { cellMap->RemoveRows(&aPresContext, aFirstRowIndex, aNumRowsToRemove, aConsiderSpans); // only remove cols that are of type eTypeAnonymous cell (they are at the end) PRInt32 numColsInMap = GetColCount(); // cell map's notion of num cols PRInt32 numColsInCache = mColFrames.Count(); PRInt32 numColsNotRemoved = DestroyAnonymousColFrames(aPresContext, numColsInCache - numColsInMap); // if the cell map has fewer cols than the cache, correct it if (numColsNotRemoved > 0) { cellMap->AddColsAtEnd(numColsNotRemoved); } } AdjustRowIndices(&aPresContext, aFirstRowIndex, -aNumRowsToRemove); //printf("removeRowsAfter\n"); //Dump(PR_TRUE, PR_FALSE, PR_TRUE); } void nsTableFrame::AppendRowGroups(nsIPresContext& aPresContext, nsIFrame* aFirstRowGroupFrame) { nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { PRInt32 rowIndex = cellMap->GetRowCount(); InsertRowGroups(aPresContext, aFirstRowGroupFrame, rowIndex); } } nsTableRowGroupFrame* nsTableFrame::GetRowGroupFrame(nsIFrame* aFrame, nsIAtom* aFrameTypeIn) { nsIFrame* rgFrame = nsnull; nsIAtom* frameType = aFrameTypeIn; if (!aFrameTypeIn) { aFrame->GetFrameType(&frameType); } if (nsLayoutAtoms::tableRowGroupFrame == frameType) { rgFrame = aFrame; } else if (nsLayoutAtoms::scrollFrame == frameType) { nsIScrollableFrame* scrollable = nsnull; nsresult rv = aFrame->QueryInterface(kIScrollableFrameIID, (void **)&scrollable); if (NS_SUCCEEDED(rv) && (scrollable)) { nsIFrame* scrolledFrame; scrollable->GetScrolledFrame(nsnull, scrolledFrame); if (scrolledFrame) { nsIAtom* scrolledType; scrolledFrame->GetFrameType(&scrolledType); if (nsLayoutAtoms::tableRowGroupFrame == scrolledType) { rgFrame = scrolledFrame; } NS_IF_RELEASE(scrolledType); } } } if (!aFrameTypeIn) { NS_IF_RELEASE(frameType); } return (nsTableRowGroupFrame*)rgFrame; } // collect the rows ancestors of aFrame PRInt32 nsTableFrame::CollectRows(nsIPresContext* aPresContext, nsIFrame* aFrame, nsVoidArray& aCollection) { if (!aFrame) return 0; PRInt32 numRows = 0; nsTableRowGroupFrame* rgFrame = GetRowGroupFrame(aFrame); if (rgFrame) { nsIFrame* childFrame = nsnull; rgFrame->FirstChild(aPresContext, nsnull, &childFrame); while (childFrame) { nsIAtom* childType; childFrame->GetFrameType(&childType); if (nsLayoutAtoms::tableRowFrame == childType) { aCollection.AppendElement(childFrame); numRows++; } else { numRows += CollectRows(aPresContext, childFrame, aCollection); } NS_IF_RELEASE(childType); childFrame->GetNextSibling(&childFrame); } } return numRows; } void nsTableFrame::InsertRowGroups(nsIPresContext& aPresContext, nsIFrame* aFirstRowGroupFrame, PRInt32 aRowIndex) { nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { PRInt32 rowIndex = aRowIndex; nsVoidArray rows; for (nsIFrame* kidFrame = aFirstRowGroupFrame; kidFrame; kidFrame->GetNextSibling(&kidFrame)) { nsTableRowGroupFrame* rgFrame = GetRowGroupFrame(kidFrame); if (rgFrame) { // create and add the cell map for the row group cellMap->InsertGroupCellMap(rowIndex, *rgFrame); // collect the new row frames in an array and add them to the table PRInt32 numRows = CollectRows(&aPresContext, kidFrame, rows); if (numRows > 0) { InsertRows(aPresContext, *rgFrame, rows, rowIndex, PR_TRUE); rowIndex += numRows; rows.Clear(); } } } } } /* ***** Column Layout Data methods ***** */ /* * Lists the column layout data which turns * around and lists the cell layout data. * This is for debugging purposes only. */ #ifdef NS_DEBUG void nsTableFrame::ListColumnLayoutData(FILE* out, PRInt32 aIndent) { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); if (this!=firstInFlow) { firstInFlow->ListColumnLayoutData(out, aIndent); return; } nsTableCellMap *cellMap = GetCellMap(); if (nsnull!=cellMap) { fprintf(out,"Column Layout Data \n"); PRInt32 numCols = cellMap->GetColCount(); PRInt32 numRows = cellMap->GetRowCount(); for (PRInt32 colIndex = 0; colIndex= 0; ) fputs(" ", out); fprintf(out,"Column Data [%d] \n",colIndex); for (PRInt32 rowIndex = 0; rowIndex < numRows; rowIndex++) { // nsTableCellFrame* cellFrame = cellMap->GetCellInfoAt(rowIndex, colIndex); PRInt32 rowIndent; for (rowIndent = aIndent+2; --rowIndent >= 0; ) fputs(" ", out); fprintf(out,"Cell Data [%d] \n",rowIndex); for (rowIndent = aIndent+2; --rowIndent >= 0; ) fputs(" ", out); } } } } #endif ///////////////////////////////////////////////////////////////////////////// // Child frame enumeration NS_IMETHODIMP nsTableFrame::FirstChild(nsIPresContext* aPresContext, nsIAtom* aListName, nsIFrame** aFirstChild) const { NS_PRECONDITION(nsnull != aFirstChild, "null OUT parameter pointer"); if (aListName == nsLayoutAtoms::colGroupList) { *aFirstChild = mColGroups.FirstChild(); return NS_OK; } return nsHTMLContainerFrame::FirstChild(aPresContext, aListName, aFirstChild); } NS_IMETHODIMP nsTableFrame::GetAdditionalChildListName(PRInt32 aIndex, nsIAtom** aListName) const { NS_PRECONDITION(nsnull != aListName, "null OUT parameter pointer"); if (aIndex < 0) { return NS_ERROR_INVALID_ARG; } *aListName = nsnull; switch (aIndex) { case NS_TABLE_FRAME_COLGROUP_LIST_INDEX: *aListName = nsLayoutAtoms::colGroupList; NS_ADDREF(*aListName); break; } return NS_OK; } /* SEC: TODO: adjust the rect for captions */ NS_METHOD nsTableFrame::Paint(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsFramePaintLayer aWhichLayer) { // table paint code is concerned primarily with borders and bg color if (NS_FRAME_PAINT_LAYER_BACKGROUND == aWhichLayer) { const nsStyleDisplay* disp = (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); if (disp->IsVisibleOrCollapsed()) { const nsStyleSpacing* spacing = (const nsStyleSpacing*)mStyleContext->GetStyleData(eStyleStruct_Spacing); const nsStyleColor* color = (const nsStyleColor*)mStyleContext->GetStyleData(eStyleStruct_Color); nsRect rect(0, 0, mRect.width, mRect.height); nsCompatibility mode; aPresContext->GetCompatibilityMode(&mode); if (eCompatibility_Standard == mode) { nsCSSRendering::PaintBackground(aPresContext, aRenderingContext, this, aDirtyRect, rect, *color, *spacing, 0, 0); // paint the column groups and columns nsIFrame* colGroupFrame = mColGroups.FirstChild(); while (nsnull != colGroupFrame) { PaintChild(aPresContext, aRenderingContext, aDirtyRect, colGroupFrame, aWhichLayer); colGroupFrame->GetNextSibling(&colGroupFrame); } } PRIntn skipSides = GetSkipSides(); if (NS_STYLE_BORDER_SEPARATE == GetBorderCollapseStyle()) { nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this, aDirtyRect, rect, *spacing, mStyleContext, skipSides); } else { //printf("paint table frame\n"); nsBorderEdges* edges = nsnull; if (mBorderCollapser) { edges = mBorderCollapser->GetEdges(); } nsCSSRendering::PaintBorderEdges(aPresContext, aRenderingContext, this, aDirtyRect, rect, edges, mStyleContext, skipSides); } } } #ifdef DEBUG // for debug... if ((NS_FRAME_PAINT_LAYER_DEBUG == aWhichLayer) && GetShowFrameBorders()) { aRenderingContext.SetColor(NS_RGB(0,255,0)); aRenderingContext.DrawRect(0, 0, mRect.width, mRect.height); } #endif PaintChildren(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer); return NS_OK; /*nsFrame::Paint(aPresContext, aRenderingContext, aDirtyRect, aWhichLayer);*/ } NS_IMETHODIMP nsTableFrame::GetFrameForPoint(nsIPresContext* aPresContext, const nsPoint& aPoint, nsFramePaintLayer aWhichLayer, nsIFrame** aFrame) { // this should act like a block, so we need to override return GetFrameForPointUsing(aPresContext, aPoint, nsnull, aWhichLayer, (aWhichLayer == NS_FRAME_PAINT_LAYER_BACKGROUND), aFrame); } //null range means the whole thing NS_IMETHODIMP nsTableFrame::SetSelected(nsIPresContext* aPresContext, nsIDOMRange *aRange, PRBool aSelected, nsSpread aSpread) { #if 0 //traverse through children unselect tables if ((aSpread == eSpreadDown)){ nsIFrame* kid; nsresult rv = FirstChild(nsnull, &kid); while (NS_SUCCEEDED(rv) && nsnull != kid) { kid->SetSelected(nsnull,aSelected,eSpreadDown); rv = kid->GetNextSibling(&kid); } } #endif // Must call base class to set mSelected state and trigger repaint of frame // Note that in current version, aRange and aSpread are ignored, // only this frame is considered nsFrame::SetSelected(aPresContext, aRange, aSelected, aSpread); return NS_OK;//return nsFrame::SetSelected(aRange,aSelected,eSpreadNone); } PRBool nsTableFrame::ParentDisablesSelection() const //override default behavior { PRBool returnval; if (NS_FAILED(GetSelected(&returnval))) return PR_FALSE; if (returnval) return PR_TRUE; return nsFrame::ParentDisablesSelection(); } PRIntn nsTableFrame::GetSkipSides() const { PRIntn skip = 0; // frame attribute was accounted for in nsHTMLTableElement::MapTableBorderInto // account for pagination if (nsnull != mPrevInFlow) { skip |= 1 << NS_SIDE_TOP; } if (nsnull != mNextInFlow) { skip |= 1 << NS_SIDE_BOTTOM; } return skip; } PRBool nsTableFrame::NeedsReflow(const nsHTMLReflowState& aReflowState) { PRBool result = PR_TRUE; if (mBits.mIsInvariantWidth) { result = PR_FALSE; } else if ((eReflowReason_Incremental == aReflowState.reason) && (NS_UNCONSTRAINEDSIZE == aReflowState.availableHeight)) { // If it's an incremental reflow and we're in galley mode, then only // do a full reflow if we need to. We need to if the cell map is invalid, // or the column info is invalid, or if we need to do a pass-1 reflow result = !(IsFirstPassValid() && IsColumnWidthsValid()); } return result; } // Called by IR_TargetIsChild() after an incremental reflow of // aKidFrame. Only called if we don't need a full reflow, e.g., the // column widths haven't changed. Not used for paginated mode, so // we don't need to worry about split row group frames // // Slides all the row groups following aKidFrame by the specified // amount // // XXX This is kind of klunky because the InnerTableReflowState::y // data member does not include the table's border/padding... nsresult nsTableFrame::AdjustSiblingsAfterReflow(nsIPresContext* aPresContext, InnerTableReflowState& aReflowState, nsIFrame* aKidFrame, nsSize* aMaxElementSize, nscoord aDeltaY) { NS_PRECONDITION(NS_UNCONSTRAINEDSIZE == aReflowState.reflowState.availableHeight, "we're not in galley mode"); // If it's the footer that was reflowed, then we don't need to adjust any of // the frames, because the footer is the bottom most frame if (aKidFrame != aReflowState.footerFrame) { nsIFrame* kidFrame; nsRect kidRect; PRBool movedFooter = PR_FALSE; // Move the frames that follow aKidFrame by aDeltaY, and update the running // y-offset for (aKidFrame->GetNextSibling(&kidFrame); kidFrame; kidFrame->GetNextSibling(&kidFrame)) { nsTableRowGroupFrame* rgFrame = GetRowGroupFrame(kidFrame); if (!rgFrame) continue; // skip foreign frames // See if it's the footer we're moving if (kidFrame == aReflowState.footerFrame) { movedFooter = PR_TRUE; } // Get the frame's bounding rect kidFrame->GetRect(kidRect); // Adjust the running y-offset aReflowState.y += kidRect.height; // Update the max element size //XXX: this should call into layout strategy to get the width field if (aMaxElementSize) { const nsStyleSpacing* tableSpacing; GetStyleData(eStyleStruct_Spacing , ((const nsStyleStruct *&)tableSpacing)); nsMargin borderPadding; GetTableBorder (borderPadding); // gets the max border thickness for each edge borderPadding += aReflowState.reflowState.mComputedPadding; nscoord cellSpacing = GetCellSpacingX(); nsSize kidMaxElementSize; rgFrame->GetMaxElementSize(kidMaxElementSize); nscoord kidWidth = kidMaxElementSize.width + borderPadding.left + borderPadding.right + cellSpacing*2; aMaxElementSize->width = PR_MAX(aMaxElementSize->width, kidWidth); aMaxElementSize->height += kidMaxElementSize.height; } // Adjust the y-origin if its position actually changed if (aDeltaY != 0) { kidRect.y += aDeltaY; kidFrame->MoveTo(aPresContext, kidRect.x, kidRect.y); } } // We also need to move the footer if there is one and we haven't already // moved it if (aReflowState.footerFrame && !movedFooter) { aReflowState.footerFrame->GetRect(kidRect); // Adjust the running y-offset aReflowState.y += kidRect.height; if (aDeltaY != 0) { kidRect.y += aDeltaY; aReflowState.footerFrame->MoveTo(aPresContext, kidRect.x, kidRect.y); } } // Invalidate the area we offset. Note that we only repaint within // our existing frame bounds. // XXX It would be better to bitblt the row frames and not repaint, // but we don't have such a view manager function yet... aKidFrame->GetRect(kidRect); if (kidRect.YMost() < mRect.height) { nsRect dirtyRect(0, kidRect.YMost(), mRect.width, mRect.height - kidRect.YMost()); Invalidate(aPresContext, dirtyRect); } } return NS_OK; } void nsTableFrame::SetColumnDimensions(nsIPresContext* aPresContext, nscoord aHeight, const nsMargin& aBorderPadding) { nscoord colHeight = aHeight -= aBorderPadding.top + aBorderPadding.bottom; nscoord cellSpacingX = GetCellSpacingX(); nscoord halfCellSpacingX = NSToCoordRound(((float)cellSpacingX) / (float)2); nsIFrame* colGroupFrame = mColGroups.FirstChild(); PRInt32 colX = 0; nsPoint colGroupOrigin(aBorderPadding.left, aBorderPadding.top); PRInt32 numCols = GetColCount(); while (nsnull != colGroupFrame) { nscoord colGroupWidth = 0; nsIFrame* colFrame = nsnull; colGroupFrame->FirstChild(aPresContext, nsnull, &colFrame); nsPoint colOrigin(0, 0); while (nsnull != colFrame) { const nsStyleDisplay* colDisplay; colFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)colDisplay)); if (NS_STYLE_DISPLAY_TABLE_COLUMN == colDisplay->mDisplay) { NS_ASSERTION(colX < numCols, "invalid number of columns"); nscoord colWidth = mColumnWidths[colX]; if (numCols == 1) { colWidth += cellSpacingX + cellSpacingX; } else if ((0 == colX) || (numCols - 1 == colX)) { colWidth += cellSpacingX + halfCellSpacingX; } else if (GetNumCellsOriginatingInCol(colX) > 0) { colWidth += cellSpacingX; } colGroupWidth += colWidth; nsRect colRect(colOrigin.x, colOrigin.y, colWidth, colHeight); colFrame->SetRect(aPresContext, colRect); colOrigin.x += colWidth; colX++; } colFrame->GetNextSibling(&colFrame); } nsRect colGroupRect(colGroupOrigin.x, colGroupOrigin.y, colGroupWidth, colHeight); colGroupFrame->SetRect(aPresContext, colGroupRect); colGroupFrame->GetNextSibling(&colGroupFrame); colGroupOrigin.x += colGroupWidth; } } // SEC: TODO need to worry about continuing frames prev/next in flow for splitting across pages. /* overview: if mFirstPassValid is false, this is our first time through since content was last changed do pass 1 get min/max info for all cells in an infinite space do column balancing do pass 2 use column widths to size table and ResizeReflow rowgroups (and therefore rows and cells) */ /* Layout the entire inner table. */ NS_METHOD nsTableFrame::Reflow(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { if (nsDebugTable::gRflTable) nsTableFrame::DebugReflow("T::Rfl en", this, &aReflowState, nsnull); // Initialize out parameter if (nsnull != aDesiredSize.maxElementSize) { aDesiredSize.maxElementSize->width = 0; aDesiredSize.maxElementSize->height = 0; } aStatus = NS_FRAME_COMPLETE; // XXX yank this nasty code and see what happens, and then yank other // similar calls to InvalidateFirstPassCache. if ((NS_UNCONSTRAINEDSIZE == aReflowState.availableWidth) && (this == (nsTableFrame *)GetFirstInFlow())) { InvalidateFirstPassCache(); } nsresult rv = NS_OK; nsSize* pass1MaxElementSize = aDesiredSize.maxElementSize; if (eReflowReason_Incremental == aReflowState.reason) { rv = IncrementalReflow(aPresContext, aDesiredSize, aReflowState, aStatus); } // If this is a style change reflow, then invalidate the pass 1 cache if (eReflowReason_StyleChange == aReflowState.reason) { InvalidateFirstPassCache(); } // NeedsReflow and IsFirstPassValid take into account reflow type = Initial_Reflow if (NeedsReflow(aReflowState)) { PRBool needsRecalc = PR_FALSE; if ((NS_UNCONSTRAINEDSIZE == aReflowState.availableWidth) || (PR_FALSE==IsFirstPassValid())) { nsReflowReason reason = aReflowState.reason; if ((eReflowReason_Initial != reason) && (eReflowReason_StyleChange != reason)) { reason = eReflowReason_Resize; } if (mBorderCollapser) { mBorderCollapser->ComputeVerticalBorders(aPresContext, 0, -1); } rv = ResizeReflowPass1(aPresContext, aDesiredSize, aReflowState, aStatus, nsnull, reason, PR_TRUE); if (NS_FAILED(rv)) return rv; needsRecalc = PR_TRUE; } if (mTableLayoutStrategy && (needsRecalc || !IsColumnWidthsValid())) { nscoord boxWidth = CalcBorderBoxWidth(aReflowState); mTableLayoutStrategy->Initialize(aPresContext, aDesiredSize.maxElementSize, boxWidth, aReflowState); mBits.mColumnWidthsValid = PR_TRUE; //so we don't do this a second time below } if (!mPrevInFlow) { // only do this for a first-in-flow table frame // assign column widths, and assign aMaxElementSize->width BalanceColumnWidths(aPresContext, aReflowState, nsSize(aReflowState.availableWidth, aReflowState.availableHeight), aDesiredSize.maxElementSize); // assign table width SetTableWidth(aPresContext, aReflowState); } if ((eReflowReason_Initial == aReflowState.reason) && (NS_UNCONSTRAINEDSIZE == aReflowState.availableWidth)) { // Don't bother doing a pass2 reflow. Use our pass1 width as our // desired width aDesiredSize.width = mRect.width; } else { // Constrain our reflow width to the computed table width. Note: this is based // on the width of the first-in-flow nsHTMLReflowState reflowState(aReflowState); PRInt32 pass1Width = mRect.width; if (mPrevInFlow) { nsTableFrame* table = (nsTableFrame*)GetFirstInFlow(); pass1Width = table->mRect.width; } reflowState.availableWidth = pass1Width; if (pass1MaxElementSize) { // we already have the max element size, so don't request it during pass2 aDesiredSize.maxElementSize = nsnull; } rv = ResizeReflowPass2(aPresContext, aDesiredSize, reflowState, aStatus); if (NS_FAILED(rv)) { return rv; } aDesiredSize.width = PR_MIN(aDesiredSize.width, pass1Width); // If this is an incremental reflow and we're here that means we had to // reflow all the rows, e.g., the column widths changed. We need to make // sure that any damaged areas are repainted if (eReflowReason_Incremental == aReflowState.reason) { nsRect damageRect; damageRect.x = 0; damageRect.y = 0; damageRect.width = mRect.width; damageRect.height = mRect.height; Invalidate(aPresContext, damageRect); } } } else { // set aDesiredSize and aMaxElementSize } SetColumnDimensions(aPresContext, aDesiredSize.height, aReflowState.mComputedBorderPadding); if (pass1MaxElementSize) { aDesiredSize.maxElementSize = pass1MaxElementSize; } // See if we are supposed to compute our maximum width if (aDesiredSize.mFlags & NS_REFLOW_CALC_MAX_WIDTH) { PRBool isAutoOrPctWidth = IsAutoLayout(&aReflowState) && ((eStyleUnit_Auto == aReflowState.mStylePosition->mWidth.GetUnit() || (eStyleUnit_Percent == aReflowState.mStylePosition->mWidth.GetUnit()))); // See if the pass1 maximum width is no longer valid because one of the // cell maximum widths changed if (isAutoOrPctWidth && !IsMaximumWidthValid()) { // Initialize the strategy and have it compute the natural size of // the table mTableLayoutStrategy->Initialize(aPresContext, nsnull, NS_UNCONSTRAINEDSIZE, aReflowState); // Now the maximum width is valid mBits.mMaximumWidthValid = PR_TRUE; // Initializing the table layout strategy assigns preliminary column // widths. We can't leave the column widths this way, and so we need to // balance the column widths to get them back to what we had previously. // XXX It would be nice to have a cleaner way to calculate the updated // maximum width BalanceColumnWidths(aPresContext, aReflowState, nsSize(aReflowState.availableWidth, aReflowState.availableHeight), aDesiredSize.maxElementSize); } if (isAutoOrPctWidth) { // Ask the strategy for the natural width of the content area aDesiredSize.mMaximumWidth = mTableLayoutStrategy->GetTableMaxContentWidth(); // Add in space for border nsMargin border; GetTableBorder (border); // this gets the max border value at every edge aDesiredSize.mMaximumWidth += border.left + border.right; // Add in space for padding aDesiredSize.mMaximumWidth += aReflowState.mComputedPadding.left + aReflowState.mComputedPadding.right; } else { // We're not auto width so the natural width is the same as the desired width aDesiredSize.mMaximumWidth = aDesiredSize.width; } } if (nsDebugTable::gRflTable) nsTableFrame::DebugReflow("T::Rfl ex", this, nsnull, &aDesiredSize, aStatus); return rv; } /** the first of 2 reflow passes * lay out the captions and row groups in an infinite space (NS_UNCONSTRAINEDSIZE) * cache the results for each caption and cell. * if successful, set mFirstPassValid=PR_TRUE, so we know we can skip this step * next time. mFirstPassValid is set to PR_FALSE when content is changed. * NOTE: should never get called on a continuing frame! All cached pass1 state * is stored in the inner table first-in-flow. */ NS_METHOD nsTableFrame::ResizeReflowPass1(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus, nsTableRowGroupFrame * aStartingFrame, nsReflowReason aReason, PRBool aDoSiblingFrames) { NS_PRECONDITION(aReflowState.frame == this, "bad reflow state"); NS_PRECONDITION(aReflowState.parentReflowState->frame == mParent, "bad parent reflow state"); NS_ASSERTION(nsnull==mPrevInFlow, "illegal call, cannot call pass 1 on a continuing frame."); NS_ASSERTION(nsnull != mContent, "null content"); nsresult rv=NS_OK; // set out params aStatus = NS_FRAME_COMPLETE; nsSize availSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); // availSize is the space available at any given time in the process nsSize maxSize(0, 0); // maxSize is the size of the largest child so far in the process nsSize kidMaxSize(0,0); nsHTMLReflowMetrics kidSize(&kidMaxSize); nscoord y = 0; // Compute the insets (sum of border and padding) // XXX: since this is pass1 reflow and where we place the rowgroup frames is irrelevant, insets are probably a waste if (IsAutoLayout(&aReflowState)) { nsIFrame* kidFrame = aStartingFrame; if (nsnull==kidFrame) kidFrame=mFrames.FirstChild(); for ( ; nsnull != kidFrame; kidFrame->GetNextSibling(&kidFrame)) { const nsStyleDisplay *childDisplay; kidFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)childDisplay)); if ((NS_STYLE_DISPLAY_TABLE_HEADER_GROUP != childDisplay->mDisplay) && (NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP != childDisplay->mDisplay) && (NS_STYLE_DISPLAY_TABLE_ROW_GROUP != childDisplay->mDisplay) ) { // it's an unknown frame type, give it a generic reflow and ignore the results nsHTMLReflowState kidReflowState(aPresContext, aReflowState, kidFrame, availSize, aReason); // rv intentionally not set here ReflowChild(kidFrame, aPresContext, kidSize, kidReflowState, 0, 0, 0, aStatus); kidFrame->DidReflow(aPresContext, NS_FRAME_REFLOW_FINISHED); continue; } // Get the row group's border padding nsMargin borderPadding; GetTableBorderForRowGroup(GetRowGroupFrameFor(kidFrame, childDisplay), borderPadding); const nsStyleSpacing* tableSpacing; GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)tableSpacing)); nsMargin padding; tableSpacing->GetPadding(padding); borderPadding += padding; // Reflow the child nsHTMLReflowState kidReflowState(aPresContext, aReflowState, kidFrame, availSize, aReason); // Note: we don't bother checking here for whether we should clear the // isTopOfPage reflow state flag, because we're dealing with an unconstrained // height and it isn't an issue... ReflowChild(kidFrame, aPresContext, kidSize, kidReflowState, borderPadding.left, borderPadding.top + y, 0, aStatus); // Place the child since some of its content fit in us. FinishReflowChild(kidFrame, aPresContext, kidSize, borderPadding.left, borderPadding.top + y, 0); if (NS_UNCONSTRAINEDSIZE==kidSize.height) // XXX This is very suspicious. Why would a row group frame want // such a large height? y = NS_UNCONSTRAINEDSIZE; else y += kidSize.height; if (kidMaxSize.width > maxSize.width) { maxSize.width = kidMaxSize.width; } if (kidMaxSize.height > maxSize.height) { maxSize.height = kidMaxSize.height; } if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { // If the child didn't finish layout then it means that it used // up all of our available space (or needs us to split). break; } if (PR_FALSE==aDoSiblingFrames) break; } // if required, give the colgroups their initial reflows if (PR_TRUE==aDoSiblingFrames) { kidFrame=mColGroups.FirstChild(); for ( ; nsnull != kidFrame; kidFrame->GetNextSibling(&kidFrame)) { nsHTMLReflowState kidReflowState(aPresContext, aReflowState, kidFrame, availSize, aReason); ReflowChild(kidFrame, aPresContext, kidSize, kidReflowState, 0, 0, 0, aStatus); FinishReflowChild(kidFrame, aPresContext, kidSize, 0, 0, 0); } } } // Get the table's border/padding const nsStyleSpacing* mySpacing = (const nsStyleSpacing*) mStyleContext->GetStyleData(eStyleStruct_Spacing); nsMargin tableBorderPadding; GetTableBorder (tableBorderPadding); // this gets the max border thickness at each edge nsMargin tablePadding; mySpacing->GetPadding(tablePadding); tableBorderPadding += tablePadding; aDesiredSize.width = kidSize.width; nscoord defaultHeight = y + tableBorderPadding.top + tableBorderPadding.bottom; aDesiredSize.height = ComputeDesiredHeight(aPresContext, aReflowState, defaultHeight); mBits.mFirstPassValid = PR_TRUE; return rv; } nsIFrame* nsTableFrame::GetFirstBodyRowGroupFrame() { nsIFrame* headerFrame = nsnull; nsIFrame* footerFrame = nsnull; for (nsIFrame* kidFrame = mFrames.FirstChild(); nsnull != kidFrame; ) { const nsStyleDisplay *childDisplay; kidFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)childDisplay)); // We expect the header and footer row group frames to be first, and we only // allow one header and one footer if (NS_STYLE_DISPLAY_TABLE_HEADER_GROUP == childDisplay->mDisplay) { if (headerFrame) { // We already have a header frame and so this header frame is treated // like an ordinary body row group frame return kidFrame; } headerFrame = kidFrame; } else if (NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP == childDisplay->mDisplay) { if (footerFrame) { // We already have a footer frame and so this footer frame is treated // like an ordinary body row group frame return kidFrame; } footerFrame = kidFrame; } else if (NS_STYLE_DISPLAY_TABLE_ROW_GROUP == childDisplay->mDisplay) { return kidFrame; } // Get the next child kidFrame->GetNextSibling(&kidFrame); } return nsnull; } // Table specific version that takes into account repeated header and footer // frames when continuing table frames void nsTableFrame::PushChildren(nsIPresContext* aPresContext, 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); if (nsnull != mNextInFlow) { nsTableFrame* nextInFlow = (nsTableFrame*)mNextInFlow; // Insert the frames after any repeated header and footer frames nsIFrame* firstBodyFrame = nextInFlow->GetFirstBodyRowGroupFrame(); nsIFrame* prevSibling = nsnull; if (firstBodyFrame) { prevSibling = nextInFlow->mFrames.GetPrevSiblingFor(firstBodyFrame); } // When pushing and pulling frames we need to check for whether any // views need to be reparented. for (nsIFrame* f = aFromChild; f; f->GetNextSibling(&f)) { nsHTMLContainerFrame::ReparentFrameView(aPresContext, f, this, nextInFlow); } nextInFlow->mFrames.InsertFrames(mNextInFlow, prevSibling, aFromChild); } else { // Add the frames to our overflow list SetOverflowFrames(aPresContext, aFromChild); } } // Table specific version that takes into account header and footer row group // frames that are repeated for continuing table frames // // Appends the overflow frames to the end of the child list, just like the // nsContainerFrame version does, except that there are no assertions that // the child list is empty (it may not be empty, because there may be repeated // header/footer frames) PRBool nsTableFrame::MoveOverflowToChildList(nsIPresContext* aPresContext) { PRBool result = PR_FALSE; // Check for an overflow list with our prev-in-flow nsTableFrame* prevInFlow = (nsTableFrame*)mPrevInFlow; if (nsnull != prevInFlow) { nsIFrame* prevOverflowFrames = prevInFlow->GetOverflowFrames(aPresContext, PR_TRUE); if (prevOverflowFrames) { // When pushing and pulling frames we need to check for whether any // views need to be reparented. for (nsIFrame* f = prevOverflowFrames; f; f->GetNextSibling(&f)) { nsHTMLContainerFrame::ReparentFrameView(aPresContext, f, prevInFlow, this); } mFrames.InsertFrames(this, nsnull, prevOverflowFrames); result = PR_TRUE; } } // It's also possible that we have an overflow list for ourselves nsIFrame* overflowFrames = GetOverflowFrames(aPresContext, PR_TRUE); if (overflowFrames) { mFrames.AppendFrames(nsnull, overflowFrames); result = PR_TRUE; } return result; } /** the second of 2 reflow passes */ NS_METHOD nsTableFrame::ResizeReflowPass2(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { NS_PRECONDITION(aReflowState.frame == this, "bad reflow state"); NS_PRECONDITION(aReflowState.parentReflowState->frame == mParent, "bad parent reflow state"); nsresult rv = NS_OK; // set out param aStatus = NS_FRAME_COMPLETE; nsMargin borderPadding; GetTableBorder (borderPadding); // this gets the max border thickness at each edge borderPadding += aReflowState.mComputedPadding; InnerTableReflowState state(aPresContext, aReflowState, borderPadding); // now that we've computed the column width information, reflow all children #ifdef NS_DEBUG //PreReflowCheck(); #endif // Check for an overflow list, and append any row group frames being // pushed MoveOverflowToChildList(aPresContext); // Reflow the existing frames if (mFrames.NotEmpty()) { ComputePercentBasisForRows(aReflowState); rv = ReflowMappedChildren(aPresContext, aDesiredSize, state, aStatus); } // Did we successfully reflow our mapped children? if (NS_FRAME_COMPLETE == aStatus) { // Any space left? if (state.availSize.height > 0) { // Try and pull-up some children from a next-in-flow rv = PullUpChildren(aPresContext, aDesiredSize, state, aStatus); } } // Return our size and our status aDesiredSize.width = ComputeDesiredWidth(aReflowState); nscoord defaultHeight = state.y + borderPadding.top + borderPadding.bottom; aDesiredSize.height = ComputeDesiredHeight(aPresContext, aReflowState, defaultHeight); AdjustForCollapsingRows(aPresContext, aDesiredSize.height); AdjustForCollapsingCols(aPresContext, aDesiredSize.width); // once horizontal borders are computed and all row heights are set, // we need to fix up length of vertical edges // XXX need to figure start row and end row correctly if ((NS_STYLE_BORDER_COLLAPSE == GetBorderCollapseStyle()) && mBorderCollapser) { mBorderCollapser->DidComputeHorizontalBorders(aPresContext, 0, 10000); } #ifdef NS_DEBUG //PostReflowCheck(aStatus); #endif return rv; } void nsTableFrame::ComputePercentBasisForRows(const nsHTMLReflowState& aReflowState) { nscoord height = CalcBorderBoxHeight(aReflowState, PR_TRUE); if ((height > 0) && (height != NS_UNCONSTRAINEDSIZE)) { // exclude our border and padding nsMargin borderPadding = aReflowState.mComputedBorderPadding; height -= borderPadding.top + borderPadding.bottom; // exclude cell spacing for all rows height -= (1 + GetRowCount()) * GetCellSpacingY(); height = PR_MAX(0, height); } else { height = 0; } mPercentBasisForRows = height; } NS_METHOD nsTableFrame::CollapseRowGroupIfNecessary(nsIPresContext* aPresContext, nsIFrame* aRowGroupFrame, const nscoord& aYTotalOffset, nscoord& aYGroupOffset, PRInt32& aRowX) { const nsStyleDisplay* groupDisplay; aRowGroupFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)groupDisplay)); PRBool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupDisplay->mVisible); nsIFrame* rowFrame; aRowGroupFrame->FirstChild(aPresContext, nsnull, &rowFrame); while (nsnull != rowFrame) { const nsStyleDisplay* rowDisplay; rowFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)rowDisplay)); if (NS_STYLE_DISPLAY_TABLE_ROW_GROUP == rowDisplay->mDisplay) { CollapseRowGroupIfNecessary(aPresContext, rowFrame, aYTotalOffset, aYGroupOffset, aRowX); } else if (NS_STYLE_DISPLAY_TABLE_ROW == rowDisplay->mDisplay) { nsRect rowRect; rowFrame->GetRect(rowRect); if (collapseGroup || (NS_STYLE_VISIBILITY_COLLAPSE == rowDisplay->mVisible)) { aYGroupOffset += rowRect.height; rowRect.height = 0; rowFrame->SetRect(aPresContext, rowRect); nsIFrame* cellFrame; rowFrame->FirstChild(aPresContext, nsnull, &cellFrame); while (nsnull != cellFrame) { const nsStyleDisplay* cellDisplay; cellFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)cellDisplay)); if (NS_STYLE_DISPLAY_TABLE_CELL == cellDisplay->mDisplay) { nsTableCellFrame* cFrame = (nsTableCellFrame*)cellFrame; nsRect cRect; cFrame->GetRect(cRect); cRect.height -= rowRect.height; cFrame->SetCollapseOffsetY(aPresContext, -aYGroupOffset); cFrame->SetRect(aPresContext, cRect); } cellFrame->GetNextSibling(&cellFrame); } // check if a cell above spans into here nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { PRInt32 numCols = cellMap->GetColCount(); nsTableCellFrame* lastCell = nsnull; for (int colX = 0; colX < numCols; colX++) { CellData* cellData = cellMap->GetCellAt(aRowX, colX); if (cellData && cellData->IsSpan()) { // a cell above is spanning into here // adjust the real cell's rect only once nsTableCellFrame* realCell = nsnull; if (cellData->IsRowSpan()) realCell = cellMap->GetCellFrame(aRowX, colX, *cellData, PR_TRUE); if (realCell != lastCell) { nsRect realRect; realCell->GetRect(realRect); realRect.height -= rowRect.height; realCell->SetRect(aPresContext, realRect); } lastCell = realCell; } } } } else { // row is not collapsed but needs to be adjusted by those that are rowRect.y -= aYGroupOffset; rowFrame->SetRect(aPresContext, rowRect); } aRowX++; } rowFrame->GetNextSibling(&rowFrame); } // end row frame while nsRect groupRect; aRowGroupFrame->GetRect(groupRect); groupRect.height -= aYGroupOffset; groupRect.y -= aYTotalOffset; aRowGroupFrame->SetRect(aPresContext, groupRect); return NS_OK; } // collapsing row groups, rows, col groups and cols are accounted for after both passes of // reflow so that it has no effect on the calculations of reflow. NS_METHOD nsTableFrame::AdjustForCollapsingRows(nsIPresContext* aPresContext, nscoord& aHeight) { nsIFrame* groupFrame = mFrames.FirstChild(); nscoord yGroupOffset = 0; // total offset among rows within a single row group nscoord yTotalOffset = 0; // total offset among all rows in all row groups PRInt32 rowIndex = 0; // collapse the rows and/or row groups as necessary while (nsnull != groupFrame) { const nsStyleDisplay* groupDisplay; groupFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)groupDisplay)); if (IsRowGroup(groupDisplay->mDisplay)) { CollapseRowGroupIfNecessary(aPresContext, groupFrame, yTotalOffset, yGroupOffset, rowIndex); } yTotalOffset += yGroupOffset; yGroupOffset = 0; groupFrame->GetNextSibling(&groupFrame); } aHeight -= yTotalOffset; return NS_OK; } NS_METHOD nsTableFrame::AdjustForCollapsingCols(nsIPresContext* aPresContext, nscoord& aWidth) { nsTableCellMap* cellMap = GetCellMap(); if (!cellMap) return NS_OK; PRInt32 numRows = cellMap->GetRowCount(); nsTableIterator groupIter(mColGroups, eTableDIR); nsIFrame* groupFrame = groupIter.First(); nscoord cellSpacingX = GetCellSpacingX(); nscoord xOffset = 0; PRInt32 colX = (groupIter.IsLeftToRight()) ? 0 : GetColCount() - 1; PRInt32 direction = (groupIter.IsLeftToRight()) ? 1 : -1; // iterate over the col groups while (nsnull != groupFrame) { const nsStyleDisplay* groupDisplay; groupFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)groupDisplay)); PRBool collapseGroup = (NS_STYLE_VISIBILITY_COLLAPSE == groupDisplay->mVisible); nsTableIterator colIter(aPresContext, *groupFrame, eTableDIR); nsIFrame* colFrame = colIter.First(); // iterate over the cols in the col group while (nsnull != colFrame) { const nsStyleDisplay* colDisplay; colFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)colDisplay)); if (NS_STYLE_DISPLAY_TABLE_COLUMN == colDisplay->mDisplay) { PRBool collapseCol = (NS_STYLE_VISIBILITY_COLLAPSE == colDisplay->mVisible); PRInt32 colWidth = GetColumnWidth(colX); if (collapseGroup || collapseCol) { xOffset += colWidth + cellSpacingX; } nsTableCellFrame* lastCell = nsnull; nsTableCellFrame* cellFrame = nsnull; for (PRInt32 rowX = 0; rowX < numRows; rowX++) { CellData* cellData = cellMap->GetCellAt(rowX, colX); nsRect cellRect; if (cellData) { if (cellData->IsOrig()) { // the cell originates at (rowX, colX) cellFrame = cellData->GetCellFrame(); // reset the collapse offsets since they may have been collapsed previously cellFrame->SetCollapseOffsetX(aPresContext, 0); cellFrame->SetCollapseOffsetY(aPresContext, 0); cellFrame->GetRect(cellRect); if (collapseGroup || collapseCol) { if (lastCell != cellFrame) { // do it only once if there is a row span cellRect.width -= colWidth; cellFrame->SetCollapseOffsetX(aPresContext, -xOffset); } } else { // the cell is not in a collapsed col but needs to move cellRect.x -= xOffset; } cellFrame->SetRect(aPresContext, cellRect); // if the cell does not originate at (rowX, colX), adjust the real cells width } else if (collapseGroup || collapseCol) { if (cellData->IsColSpan()) { cellFrame = cellMap->GetCellFrame(rowX, colX, *cellData, PR_FALSE); } if ((cellFrame) && (lastCell != cellFrame)) { cellFrame->GetRect(cellRect); cellRect.width -= colWidth + cellSpacingX; cellFrame->SetRect(aPresContext, cellRect); } } } lastCell = cellFrame; } colX += direction; } colFrame = colIter.Next(); } // inner while groupFrame = groupIter.Next(); } // outer while aWidth -= xOffset; return NS_OK; } // Sets the starting column index for aColGroupFrame and the siblings frames that // follow void nsTableFrame::SetStartingColumnIndexFor(nsTableColGroupFrame* aColGroupFrame, PRInt32 aIndex) { while (aColGroupFrame) { aIndex += aColGroupFrame->SetStartColumnIndex(aIndex); aColGroupFrame->GetNextSibling((nsIFrame**)&aColGroupFrame); } } // Calculate the starting column index to use for the specified col group frame PRInt32 nsTableFrame::CalculateStartingColumnIndexFor(nsTableColGroupFrame* aColGroupFrame) { PRInt32 index = 0; for (nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame*)mColGroups.FirstChild(); colGroupFrame && (colGroupFrame != aColGroupFrame); colGroupFrame->GetNextSibling((nsIFrame**)&colGroupFrame)) { index += colGroupFrame->GetColCount(); } return index; } NS_IMETHODIMP nsTableFrame::AppendFrames(nsIPresContext* aPresContext, nsIPresShell& aPresShell, nsIAtom* aListName, nsIFrame* aFrameList) { PRInt32 startColIndex = 0; // Because we actually have two child lists, one for col group frames and one // for everything else, we need to look at each frame individually nsIFrame* f = aFrameList; nsIFrame* firstAppendedColGroup = nsnull; while (f) { nsIFrame* next; // Get the next frame and disconnect this frame from its sibling f->GetNextSibling(&next); f->SetNextSibling(nsnull); // See what kind of frame we have const nsStyleDisplay *display; f->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)display)); if (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == display->mDisplay) { // Append the new col group frame mColGroups.AppendFrame(nsnull, f); if (!firstAppendedColGroup) { firstAppendedColGroup = f; nsIFrame* lastChild = mFrames.LastChild(); nsTableColGroupFrame* lastColGroup = (nsTableColGroupFrame*)GetFrameAtOrBefore(aPresContext, this, lastChild, nsLayoutAtoms::tableColGroupFrame); startColIndex = (lastColGroup) ? lastColGroup->GetStartColumnIndex() + lastColGroup->GetColCount() : 0; } } else if (IsRowGroup(display->mDisplay)) { // insert the rows into the table InsertRowGroups(*aPresContext, f, GetRowCount()); // Append the new row group frame to the sibling chain mFrames.AppendFrame(nsnull, f); } else { // Nothing special to do, just add the frame to our child list mFrames.AppendFrame(nsnull, f); } // Move to the next frame f = next; } if (firstAppendedColGroup) { InsertColGroups(*aPresContext, startColIndex, firstAppendedColGroup); } // We'll need to do a pass-1 layout of all cells in all the rows of the // rowgroup. XXX - fix this InvalidateFirstPassCache(); // Because the number of columns may have changed invalidate them InvalidateColumnWidths(); // Mark the table as dirty and generate a reflow command targeted at the // outer table frame nsIReflowCommand* reflowCmd; nsresult rv; mState |= NS_FRAME_IS_DIRTY; rv = NS_NewHTMLReflowCommand(&reflowCmd, mParent, nsIReflowCommand::ReflowDirty); if (NS_SUCCEEDED(rv)) { // Add the reflow command rv = aPresShell.AppendReflowCommand(reflowCmd); NS_RELEASE(reflowCmd); } return NS_OK; } NS_IMETHODIMP nsTableFrame::InsertFrames(nsIPresContext* aPresContext, nsIPresShell& aPresShell, nsIAtom* aListName, nsIFrame* aPrevFrame, nsIFrame* aFrameList) { // Asssume there's only one frame being inserted. The problem is that // row group frames and col group frames go in separate child lists and // so if there's more than one this gets messy... // XXX The frame construction code should be separating out child frames // based on the type... nsIFrame* nextSibling; aFrameList->GetNextSibling(&nextSibling); NS_PRECONDITION(!nextSibling, "expected only one child frame"); // See what kind of frame we have const nsStyleDisplay *display=nsnull; aFrameList->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)display)); if (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == display->mDisplay) { // Insert the column group frame nsFrameList frames(aFrameList); // convience for getting last frame nsIFrame* lastFrame = frames.LastChild(); mColGroups.InsertFrame(nsnull, aPrevFrame, aFrameList); // find the starting col index for the first new col group PRInt32 startColIndex = 0; if (aPrevFrame) { nsTableColGroupFrame* prevColGroup = (nsTableColGroupFrame*)GetFrameAtOrBefore(aPresContext, this, aPrevFrame, nsLayoutAtoms::tableColGroupFrame); if (prevColGroup) { startColIndex = prevColGroup->GetStartColumnIndex() + prevColGroup->GetColCount(); } } InsertColGroups(*aPresContext, startColIndex, aFrameList, lastFrame); } else if (IsRowGroup(display->mDisplay)) { // get the starting row index of the new rows and insert them into the table PRInt32 rowIndex = 0; nsTableRowGroupFrame* prevRowGroup = (nsTableRowGroupFrame *)nsTableFrame::GetFrameAtOrBefore(aPresContext, this, aPrevFrame, nsLayoutAtoms::tableRowGroupFrame); if (prevRowGroup) { PRInt32 numRows; prevRowGroup->GetRowCount(numRows); rowIndex = prevRowGroup->GetStartRowIndex() + numRows; } InsertRowGroups(*aPresContext, aFrameList, rowIndex); // Insert the frames in the sibling chain mFrames.InsertFrame(nsnull, aPrevFrame, aFrameList); } else { // Just insert the frame and don't worry about reflowing it mFrames.InsertFrame(nsnull, aPrevFrame, aFrameList); return NS_OK; } // Because the number of columns may have changed invalidate them InvalidateColumnWidths(); InvalidateFirstPassCache(); // XXX fix this // Mark the table as dirty and generate a reflow command targeted at the // outer table frame nsIReflowCommand* reflowCmd; nsresult rv; mState |= NS_FRAME_IS_DIRTY; rv = NS_NewHTMLReflowCommand(&reflowCmd, mParent, nsIReflowCommand::ReflowDirty); if (NS_SUCCEEDED(rv)) { // Add the reflow command rv = aPresShell.AppendReflowCommand(reflowCmd); NS_RELEASE(reflowCmd); } return rv; return NS_OK; } NS_IMETHODIMP nsTableFrame::RemoveFrame(nsIPresContext* aPresContext, nsIPresShell& aPresShell, nsIAtom* aListName, nsIFrame* aOldFrame) { // See what kind of frame we have const nsStyleDisplay *display=nsnull; aOldFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)display)); if (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == display->mDisplay) { nsIFrame* nextColGroupFrame; aOldFrame->GetNextSibling(&nextColGroupFrame); nsTableColGroupFrame* colGroup = (nsTableColGroupFrame*)aOldFrame; PRInt32 firstColIndex = colGroup->GetStartColumnIndex(); PRInt32 lastColIndex = firstColIndex + colGroup->GetColCount() - 1; // remove the col frames, the colGroup frame and reset col indices colGroup->RemoveChildrenAtEnd(*aPresContext, colGroup->GetColCount()); mColGroups.DestroyFrame(aPresContext, aOldFrame); nsTableColGroupFrame::ResetColIndices(aPresContext, nextColGroupFrame, firstColIndex); // remove the cols from the table PRInt32 colX; for (colX = lastColIndex; colX >= firstColIndex; colX--) { nsTableColFrame* colFrame = (nsTableColFrame*)mColFrames.ElementAt(colX); if (colFrame) { RemoveCol(*aPresContext, colGroup, colX, PR_TRUE, PR_FALSE); } } for (colX = lastColIndex; colX >= firstColIndex; colX--) { nsTableColFrame* colFrame = (nsTableColFrame*)mColFrames.ElementAt(colX); if (colFrame) { RemoveCol(*aPresContext, colGroup, colX, PR_FALSE, PR_TRUE); } } PRInt32 numAnonymousColsToAdd = GetColCount() - mColFrames.Count(); if (numAnonymousColsToAdd > 0) { // this sets the child list, updates the col cache and cell map CreateAnonymousColFrames(*aPresContext, numAnonymousColsToAdd, eColAnonymousCell, PR_TRUE); } InvalidateColumnWidths(); AddTableDirtyReflowCommand(aPresContext, this); } else { nsTableRowGroupFrame* rgFrame = GetRowGroupFrame(aOldFrame); if (rgFrame) { PRInt32 startRowIndex = rgFrame->GetStartRowIndex(); PRInt32 numRows; rgFrame->GetRowCount(numRows, PR_TRUE); // remove the row group from the cell map nsTableCellMap* cellMap = GetCellMap(); if (cellMap) { cellMap->RemoveGroupCellMap(rgFrame); } // only remove cols that are of type eTypeAnonymous cell (they are at the end) PRInt32 numColsInMap = GetColCount(); // cell map's notion of num cols PRInt32 numColsInCache = mColFrames.Count(); PRInt32 numColsNotRemoved = DestroyAnonymousColFrames(*aPresContext, numColsInCache - numColsInMap); // if the cell map has fewer cols than the cache, correct it if (numColsNotRemoved > 0) { cellMap->AddColsAtEnd(numColsNotRemoved); } AdjustRowIndices(aPresContext, startRowIndex, -numRows); // remove the row group frame from the sibling chain mFrames.DestroyFrame(aPresContext, aOldFrame); InvalidateColumnWidths(); AddTableDirtyReflowCommand(aPresContext, this); } else { // Just remove the frame mFrames.DestroyFrame(aPresContext, aOldFrame); return NS_OK; } } return NS_OK; } NS_METHOD nsTableFrame::IncrementalReflow(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { nsresult rv = NS_OK; // Constrain our reflow width to the computed table width. Note: this is // based on the width of the first-in-flow nsHTMLReflowState reflowState(aReflowState); PRInt32 pass1Width = mRect.width; if (mPrevInFlow) { nsTableFrame* table = (nsTableFrame*)GetFirstInFlow(); pass1Width = table->mRect.width; } reflowState.availableWidth = pass1Width; // get border + padding nsMargin borderPadding; GetTableBorder (borderPadding); borderPadding += aReflowState.mComputedPadding; InnerTableReflowState state(aPresContext, reflowState, borderPadding); // determine if this frame is the target or not nsIFrame *target=nsnull; rv = reflowState.reflowCommand->GetTarget(target); if ((PR_TRUE==NS_SUCCEEDED(rv)) && (nsnull!=target)) { // this is the target if target is either this or the outer table frame containing this inner frame nsIFrame *outerTableFrame=nsnull; GetParent(&outerTableFrame); if ((this==target) || (outerTableFrame==target)) rv = IR_TargetIsMe(aPresContext, aDesiredSize, state, aStatus); else { // Get the next frame in the reflow chain nsIFrame* nextFrame; reflowState.reflowCommand->GetNext(nextFrame); NS_ASSERTION(nextFrame, "next frame in reflow command is null"); // Recover our reflow state rv = IR_TargetIsChild(aPresContext, aDesiredSize, state, aStatus, nextFrame); } } return rv; } // this assumes the dirty children are contiguous PRBool GetDirtyChildren(nsIPresContext* aPresContext, nsIFrame* aFrame, nsIFrame** aFirstDirty, PRInt32& aNumDirty) { *aFirstDirty = nsnull; aNumDirty = 0; nsIFrame* kidFrame; aFrame->FirstChild(aPresContext, nsnull, &kidFrame); while (kidFrame) { nsFrameState frameState; kidFrame->GetFrameState(&frameState); if (frameState & NS_FRAME_IS_DIRTY) { if (!*aFirstDirty) { *aFirstDirty = kidFrame; } aNumDirty++; } } return (aNumDirty > 0); } NS_METHOD nsTableFrame::IR_TargetIsMe(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, InnerTableReflowState& aReflowState, nsReflowStatus& aStatus) { nsresult rv = NS_OK; aStatus = NS_FRAME_COMPLETE; nsIReflowCommand::ReflowType type; aReflowState.reflowState.reflowCommand->GetType(type); nsIFrame *objectFrame; aReflowState.reflowState.reflowCommand->GetChildFrame(objectFrame); const nsStyleDisplay *childDisplay=nsnull; if (nsnull!=objectFrame) objectFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)childDisplay)); switch (type) { case nsIReflowCommand::StyleChanged : rv = IR_StyleChanged(aPresContext, aDesiredSize, aReflowState, aStatus); break; case nsIReflowCommand::ContentChanged : NS_ASSERTION(PR_FALSE, "illegal reflow type: ContentChanged"); rv = NS_ERROR_ILLEGAL_VALUE; break; case nsIReflowCommand::ReflowDirty: { //nsIFrame* dirtyChild; //PRInt32 numDirty; //if (GetDirtyChildren(this, &dirtyChild, numDirty)) { // for (PRInt32 dirtyX = 0; dirtyX < numDirty; dirtyX++) { // ResizeReflowPass1(aPresContext, aDesiredSize, aReflowState, aStatus, // dirtyChild, reason, PR_FALSE); // dirtyChild->GetNextSibling(&dirtyChild); // } //} //else { // Problem is we don't know has changed, so assume the worst InvalidateFirstPassCache(); //} InvalidateColumnWidths(); rv = NS_OK; break; } default: NS_NOTYETIMPLEMENTED("unexpected reflow command type"); rv = NS_ERROR_NOT_IMPLEMENTED; break; } return rv; } NS_METHOD nsTableFrame::IR_StyleChanged(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, InnerTableReflowState& aReflowState, nsReflowStatus& aStatus) { nsresult rv = NS_OK; // we presume that all the easy optimizations were done in the nsHTMLStyleSheet before we were called here // XXX: we can optimize this when we know which style attribute changed // if something like border changes, we need to do pass1 again // but if something like width changes from 100 to 200, we just need to do pass2 InvalidateFirstPassCache(); return rv; } // Recovers the reflow state to what it should be if aKidFrame is about // to be reflowed. Restores the following: // - availSize // - y // - footerFrame // - firstBodySection // // In the case of the footer frame the y-offset is set to its current // y-offset. Note that this is different from resize reflow when the // footer is positioned higher up and then moves down as each row // group frame is relowed // // XXX This is kind of klunky because the InnerTableReflowState::y // data member does not include the table's border/padding... nsresult nsTableFrame::RecoverState(InnerTableReflowState& aReflowState, nsIFrame* aKidFrame, nsSize* aMaxElementSize) { // Walk the list of children looking for aKidFrame for (nsIFrame* childFrame = mFrames.FirstChild(); childFrame; childFrame->GetNextSibling(&childFrame)) { nsTableRowGroupFrame* rgFrame = GetRowGroupFrame(childFrame); if (!rgFrame) continue; // skip foreign frame types // If this is a footer row group, remember it const nsStyleDisplay *display; rgFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)display)); // We only allow a single footer frame, and the footer frame must occur before // any body section row groups if ((NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP == display->mDisplay) && !aReflowState.footerFrame && !aReflowState.firstBodySection) { aReflowState.footerFrame = childFrame; } else if ((NS_STYLE_DISPLAY_TABLE_ROW_GROUP == display->mDisplay) && !aReflowState.firstBodySection) { aReflowState.firstBodySection = childFrame; } // See if this is the frame we're looking for if (childFrame == aKidFrame) { // If it's the footer, then keep going because the footer is at the // very bottom if (childFrame != aReflowState.footerFrame) { break; } } // Get the frame's height nsSize kidSize; childFrame->GetSize(kidSize); // If our height is constrained then update the available height. Do // this for all frames including the footer frame if (PR_FALSE == aReflowState.unconstrainedHeight) { aReflowState.availSize.height -= kidSize.height; } // Update the running y-offset. Don't do this for the footer frame if (childFrame != aReflowState.footerFrame) { aReflowState.y += kidSize.height; } // Update the max element size //XXX: this should call into layout strategy to get the width field if (nsnull != aMaxElementSize) { const nsStyleSpacing* tableSpacing; GetStyleData(eStyleStruct_Spacing , ((const nsStyleStruct *&)tableSpacing)); nsMargin borderPadding; GetTableBorder (borderPadding); // gets the max border thickness for each edge borderPadding += aReflowState.reflowState.mComputedPadding; nscoord cellSpacing = GetCellSpacingX(); nsSize kidMaxElementSize; rgFrame->GetMaxElementSize(kidMaxElementSize); nscoord kidWidth = kidMaxElementSize.width + borderPadding.left + borderPadding.right + cellSpacing*2; aMaxElementSize->width = PR_MAX(aMaxElementSize->width, kidWidth); aMaxElementSize->height += kidMaxElementSize.height; } } return NS_OK; } NS_METHOD nsTableFrame::IR_TargetIsChild(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, InnerTableReflowState& aReflowState, nsReflowStatus& aStatus, nsIFrame * aNextFrame) { nsresult rv; // Recover the state as if aNextFrame is about to be reflowed RecoverState(aReflowState, aNextFrame, aDesiredSize.maxElementSize); // Remember the old rect nsRect oldKidRect; aNextFrame->GetRect(oldKidRect); // Pass along the reflow command nsSize kidMaxElementSize; nsHTMLReflowMetrics desiredSize(aDesiredSize.maxElementSize ? &kidMaxElementSize : nsnull, aDesiredSize.mFlags); nsHTMLReflowState kidReflowState(aPresContext, aReflowState.reflowState, aNextFrame, aReflowState.availSize); nscoord x = aReflowState.mBorderPadding.left; nscoord y = aReflowState.mBorderPadding.top + aReflowState.y; rv = ReflowChild(aNextFrame, aPresContext, desiredSize, kidReflowState, x, y, 0, aStatus); // Place the row group frame. Don't use PlaceChild(), because it moves // the footer frame as well. We'll adjust the footer frame later on in // AdjustSiblingsAfterReflow() nsRect kidRect(x, y, desiredSize.width, desiredSize.height); FinishReflowChild(aNextFrame, aPresContext, desiredSize, x, y, 0); // Adjust the running y-offset aReflowState.y += desiredSize.height; // If our height is constrained, then update the available height if (PR_FALSE == aReflowState.unconstrainedHeight) { aReflowState.availSize.height -= desiredSize.height; } // Update the max element size //XXX: this should call into layout strategy to get the width field if (nsnull != aDesiredSize.maxElementSize) { const nsStyleSpacing* tableSpacing; GetStyleData(eStyleStruct_Spacing , ((const nsStyleStruct *&)tableSpacing)); nsMargin borderPadding; GetTableBorder (borderPadding); // gets the max border thickness for each edge borderPadding += aReflowState.reflowState.mComputedPadding; nscoord cellSpacing = GetCellSpacingX(); nscoord kidWidth = kidMaxElementSize.width + borderPadding.left + borderPadding.right + cellSpacing*2; aDesiredSize.maxElementSize->width = PR_MAX(aDesiredSize.maxElementSize->width, kidWidth); aDesiredSize.maxElementSize->height += kidMaxElementSize.height; } // If the column width info is valid, then adjust the row group frames // that follow. Otherwise, return and we'll recompute the column widths // and reflow all the row group frames if (!NeedsReflow(aReflowState.reflowState)) { // If the row group frame changed height, then damage the horizontal strip // that was either added or went away if (desiredSize.height != oldKidRect.height) { nsRect dirtyRect; dirtyRect.x = 0; dirtyRect.y = PR_MIN(oldKidRect.YMost(), kidRect.YMost()); dirtyRect.width = mRect.width; dirtyRect.height = PR_MAX(oldKidRect.YMost(), kidRect.YMost()) - dirtyRect.y; Invalidate(aPresContext, dirtyRect); } // Adjust the row groups that follow AdjustSiblingsAfterReflow(aPresContext, aReflowState, aNextFrame, aDesiredSize.maxElementSize, desiredSize.height - oldKidRect.height); // Return our size and our status aDesiredSize.width = ComputeDesiredWidth(aReflowState.reflowState); nscoord defaultHeight = aReflowState.y + aReflowState.mBorderPadding.top + aReflowState.mBorderPadding.bottom; aDesiredSize.height = ComputeDesiredHeight(aPresContext, aReflowState.reflowState, defaultHeight); // XXX Is this needed? #if 0 AdjustForCollapsingRows(aPresContext, aDesiredSize.height); AdjustForCollapsingCols(aPresContext, aDesiredSize.width); // once horizontal borders are computed and all row heights are set, // we need to fix up length of vertical edges // XXX need to figure start row and end row correctly if ((NS_STYLE_BORDER_COLLAPSE == GetBorderCollapseStyle()) && mBorderCollapser) { mBorderCollapser->DidComputeHorizontalBorders(aPresContext, 0, 10000); } #endif } return rv; } nscoord nsTableFrame::ComputeDesiredWidth(const nsHTMLReflowState& aReflowState) const { nscoord desiredWidth = aReflowState.availableWidth; // this is the biggest hack in the world. But there's no other rational way to handle nested percent tables const nsStylePosition* position; PRBool isNested=IsNested(aReflowState, position); if((eReflowReason_Initial==aReflowState.reason) && (PR_TRUE==isNested) && (eStyleUnit_Percent==position->mWidth.GetUnit())) { nsITableLayoutStrategy* tableLayoutStrategy = mTableLayoutStrategy; if (mPrevInFlow) { // Get the table layout strategy from the first-in-flow nsTableFrame* table = (nsTableFrame*)GetFirstInFlow(); tableLayoutStrategy = table->mTableLayoutStrategy; } desiredWidth = tableLayoutStrategy->GetTableMaxContentWidth(); } return desiredWidth; } // Position and size aKidFrame and update our reflow state. The origin of // aKidRect is relative to the upper-left origin of our frame void nsTableFrame::PlaceChild(nsIPresContext* aPresContext, InnerTableReflowState& aReflowState, nsIFrame* aKidFrame, nsHTMLReflowMetrics& aDesiredSize, nscoord aX, nscoord aY, nsSize* aMaxElementSize, nsSize& aKidMaxElementSize) { // Place and size the child FinishReflowChild(aKidFrame, aPresContext, aDesiredSize, aX, aY, 0); // Adjust the running y-offset aReflowState.y += aDesiredSize.height; // If our height is constrained, then update the available height if (PR_FALSE == aReflowState.unconstrainedHeight) { aReflowState.availSize.height -= aDesiredSize.height; } // If this is a footer row group, remember it const nsStyleDisplay *childDisplay; aKidFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)childDisplay)); // We only allow a single footer frame, and the footer frame must occur before // any body section row groups if ((NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP == childDisplay->mDisplay) && !aReflowState.footerFrame && !aReflowState.firstBodySection) { aReflowState.footerFrame = aKidFrame; } else if (aReflowState.footerFrame) { // Place the row group frame nsSize footerSize; nsPoint origin; aKidFrame->GetOrigin(origin); aReflowState.footerFrame->GetSize(footerSize); origin.y -= footerSize.height; aKidFrame->MoveTo(aPresContext, origin.x, origin.y); // Move the footer below the body row group frame aReflowState.footerFrame->GetOrigin(origin); origin.y += aDesiredSize.height; aReflowState.footerFrame->MoveTo(aPresContext, origin.x, origin.y); } //XXX: this should call into layout strategy to get the width field if (nsnull != aMaxElementSize) { const nsStyleSpacing* tableSpacing; GetStyleData(eStyleStruct_Spacing , ((const nsStyleStruct *&)tableSpacing)); nsMargin borderPadding; GetTableBorder (borderPadding); // gets the max border thickness for each edge borderPadding += aReflowState.reflowState.mComputedPadding; nscoord cellSpacing = GetCellSpacingX(); nscoord kidWidth = aKidMaxElementSize.width + borderPadding.left + borderPadding.right + cellSpacing*2; aMaxElementSize->width = PR_MAX(aMaxElementSize->width, kidWidth); aMaxElementSize->height += aKidMaxElementSize.height; } } /** * Reflow the frames we've already created * * @param aPresContext presentation context to use * @param aReflowState current inline state * @return true if we successfully reflowed all the mapped children and false * otherwise, e.g. we pushed children to the next in flow */ NS_METHOD nsTableFrame::ReflowMappedChildren(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, InnerTableReflowState& aReflowState, nsReflowStatus& aStatus) { NS_PRECONDITION(mFrames.NotEmpty(), "no children"); PRInt32 childCount = 0; nsIFrame* prevKidFrame = nsnull; nsSize kidMaxElementSize(0,0); nsSize* pKidMaxElementSize = (nsnull != aDesiredSize.maxElementSize) ? &kidMaxElementSize : nsnull; nsresult rv = NS_OK; nsReflowReason reason; if (!IsAutoLayout(&aReflowState.reflowState)) { reason = aReflowState.reflowState.reason; if (eReflowReason_Incremental==reason) { reason = eReflowReason_Resize; if (aDesiredSize.maxElementSize) { aDesiredSize.maxElementSize->width = 0; aDesiredSize.maxElementSize->height = 0; } } } else reason = eReflowReason_Resize; // this never passes reflows down to colgroups for (nsIFrame* kidFrame = mFrames.FirstChild(); nsnull != kidFrame; ) { nsSize kidAvailSize(aReflowState.availSize); nsHTMLReflowMetrics desiredSize(pKidMaxElementSize); desiredSize.width=desiredSize.height=desiredSize.ascent=desiredSize.descent=0; const nsStyleDisplay *childDisplay; kidFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)childDisplay)); if (PR_TRUE==IsRowGroup(childDisplay->mDisplay)) { // Keep track of the first body section row group if (nsnull == aReflowState.firstBodySection) { if (NS_STYLE_DISPLAY_TABLE_ROW_GROUP == childDisplay->mDisplay) { aReflowState.firstBodySection = kidFrame; } } nsMargin borderPadding; GetTableBorderForRowGroup(GetRowGroupFrameFor(kidFrame, childDisplay), borderPadding); const nsStyleSpacing* tableSpacing; GetStyleData(eStyleStruct_Spacing, ((const nsStyleStruct *&)tableSpacing)); borderPadding += aReflowState.reflowState.mComputedPadding; // Reflow the child into the available space nsHTMLReflowState kidReflowState(aPresContext, aReflowState.reflowState, kidFrame, kidAvailSize, reason); if (aReflowState.firstBodySection && (kidFrame != aReflowState.firstBodySection)) { // If this isn't the first row group frame or the header or footer, then // we can't be at the top of the page anymore... kidReflowState.isTopOfPage = PR_FALSE; } nscoord x = borderPadding.left; nscoord y = borderPadding.top + aReflowState.y; if (RowGroupsShouldBeConstrained()) { // Only applies to the tree widget. nscoord tableSpecifiedHeight = CalcBorderBoxHeight(aReflowState.reflowState, PR_TRUE); if ((tableSpecifiedHeight != NS_UNCONSTRAINEDSIZE)) { kidReflowState.availableHeight = tableSpecifiedHeight - y; if (kidReflowState.availableHeight < 0) kidReflowState.availableHeight = 0; } } rv = ReflowChild(kidFrame, aPresContext, desiredSize, kidReflowState, x, y, 0, aStatus); // Did the child fit? if (desiredSize.height > kidAvailSize.height) { if (aReflowState.firstBodySection && (kidFrame != aReflowState.firstBodySection)) { // The child is too tall to fit at all in the available space, and it's // not a header/footer or our first row group frame PushChildren(aPresContext, kidFrame, prevKidFrame); aStatus = NS_FRAME_NOT_COMPLETE; break; } } // Place the child if (PR_TRUE==IsRowGroup(childDisplay->mDisplay)) { // we don't want to adjust the maxElementSize if this is an initial reflow // it was set by the TableLayoutStrategy and shouldn't be changed. nsSize *requestedMaxElementSize = nsnull; if (eReflowReason_Initial != aReflowState.reflowState.reason) requestedMaxElementSize = aDesiredSize.maxElementSize; PlaceChild(aPresContext, aReflowState, kidFrame, desiredSize, x, y, requestedMaxElementSize, kidMaxElementSize); } else { kidFrame->DidReflow(aPresContext, NS_FRAME_REFLOW_FINISHED); } childCount++; // Remember where we just were in case we end up pushing children prevKidFrame = kidFrame; // Special handling for incomplete children if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { nsIFrame* kidNextInFlow; kidFrame->GetNextInFlow(&kidNextInFlow); if (nsnull == kidNextInFlow) { // The child doesn't have a next-in-flow so create a continuing // frame. This hooks the child into the flow nsIFrame* continuingFrame; nsIPresShell* presShell; nsIStyleSet* styleSet; aPresContext->GetShell(&presShell); presShell->GetStyleSet(&styleSet); NS_RELEASE(presShell); styleSet->CreateContinuingFrame(aPresContext, kidFrame, this, &continuingFrame); NS_RELEASE(styleSet); // Add the continuing frame to the sibling list nsIFrame* nextSib; kidFrame->GetNextSibling(&nextSib); continuingFrame->SetNextSibling(nextSib); kidFrame->SetNextSibling(continuingFrame); } // We've used up all of our available space so push the remaining // children to the next-in-flow nsIFrame* nextSibling; kidFrame->GetNextSibling(&nextSibling); if (nsnull != nextSibling) { PushChildren(aPresContext, nextSibling, kidFrame); } break; } } else {// it's an unknown frame type, give it a generic reflow and ignore the results nsHTMLReflowState kidReflowState(aPresContext, aReflowState.reflowState, kidFrame, nsSize(0,0), eReflowReason_Resize); nsHTMLReflowMetrics unusedDesiredSize(nsnull); nsReflowStatus status; ReflowChild(kidFrame, aPresContext, unusedDesiredSize, kidReflowState, 0, 0, 0, status); kidFrame->DidReflow(aPresContext, NS_FRAME_REFLOW_FINISHED); } // Get the next child kidFrame->GetNextSibling(&kidFrame); } // Update the child count return rv; } /** * Try and pull-up frames from our next-in-flow * * @param aPresContext presentation context to use * @param aReflowState current inline state * @return true if we successfully pulled-up all the children and false * otherwise, e.g. child didn't fit */ NS_METHOD nsTableFrame::PullUpChildren(nsIPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, InnerTableReflowState& aReflowState, nsReflowStatus& aStatus) { nsTableFrame* nextInFlow = (nsTableFrame*)mNextInFlow; nsSize kidMaxElementSize(0,0); nsSize* pKidMaxElementSize = (nsnull != aDesiredSize.maxElementSize) ? &kidMaxElementSize : nsnull; nsIFrame* prevKidFrame = mFrames.LastChild(); nsresult rv = NS_OK; while (nsnull != nextInFlow) { nsHTMLReflowMetrics kidSize(pKidMaxElementSize); kidSize.width=kidSize.height=kidSize.ascent=kidSize.descent=0; // XXX change to use nsFrameList::PullFrame // Get the next child nsIFrame* kidFrame = nextInFlow->mFrames.FirstChild(); // Any more child frames? if (nsnull == kidFrame) { // No. Any frames on its overflow list? nsIFrame* nextOverflowFrames = nextInFlow->GetOverflowFrames(aPresContext, PR_TRUE); if (nextOverflowFrames) { // Move the overflow list to become the child list nextInFlow->mFrames.AppendFrames(nsnull, nextOverflowFrames); kidFrame = nextInFlow->mFrames.FirstChild(); } else { // We've pulled up all the children, so move to the next-in-flow. nextInFlow->GetNextInFlow((nsIFrame**)&nextInFlow); continue; } } // See if the child fits in the available space. If it fits or // it's splittable then reflow it. The reason we can't just move // it is that we still need ascent/descent information nsSize kidFrameSize(0,0); nsSplittableType kidIsSplittable; kidFrame->GetSize(kidFrameSize); kidFrame->IsSplittable(kidIsSplittable); if ((kidFrameSize.height > aReflowState.availSize.height) && NS_FRAME_IS_NOT_SPLITTABLE(kidIsSplittable)) { //XXX: Troy aStatus = NS_FRAME_NOT_COMPLETE; break; } nsHTMLReflowState kidReflowState(aPresContext, aReflowState.reflowState, kidFrame, aReflowState.availSize, eReflowReason_Resize); rv = ReflowChild(kidFrame, aPresContext, kidSize, kidReflowState, 0, aReflowState.y, 0, aStatus); // Did the child fit? if ((kidSize.height > aReflowState.availSize.height) && mFrames.NotEmpty()) { // The child is too wide to fit in the available space, and it's // not our first child //XXX: Troy aStatus = NS_FRAME_NOT_COMPLETE; break; } const nsStyleDisplay *childDisplay; kidFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)childDisplay)); if (PR_TRUE==IsRowGroup(childDisplay->mDisplay)) { PlaceChild(aPresContext, aReflowState, kidFrame, kidSize, 0, aReflowState.y, aDesiredSize.maxElementSize, *pKidMaxElementSize); } else { kidFrame->DidReflow(aPresContext, NS_FRAME_REFLOW_FINISHED); } // Remove the frame from its current parent nextInFlow->mFrames.RemoveFirstChild(); // Link the frame into our list of children kidFrame->SetParent(this); if (nsnull == prevKidFrame) { mFrames.SetFrames(kidFrame); } else { prevKidFrame->SetNextSibling(kidFrame); } kidFrame->SetNextSibling(nsnull); // Remember where we just were in case we end up pushing children prevKidFrame = kidFrame; if (NS_FRAME_IS_NOT_COMPLETE(aStatus)) { // No the child isn't complete nsIFrame* kidNextInFlow; kidFrame->GetNextInFlow(&kidNextInFlow); if (nsnull == kidNextInFlow) { // The child doesn't have a next-in-flow so create a // continuing frame. The creation appends it to the flow and // prepares it for reflow. nsIFrame* continuingFrame; nsIPresShell* presShell; nsIStyleSet* styleSet; aPresContext->GetShell(&presShell); presShell->GetStyleSet(&styleSet); NS_RELEASE(presShell); styleSet->CreateContinuingFrame(aPresContext, kidFrame, this, &continuingFrame); NS_RELEASE(styleSet); // Add the continuing frame to our sibling list and then push // it to the next-in-flow. This ensures the next-in-flow's // content offsets and child count are set properly. Note that // we can safely assume that the continuation is complete so // we pass PR_TRUE into PushChidren kidFrame->SetNextSibling(continuingFrame); PushChildren(aPresContext, continuingFrame, kidFrame); } break; } } return rv; } /** Now I've got all the cells laid out in an infinite space. For each column, use the min size for each cell in that column along with the attributes of the table, column group, and column to assign widths to each column. */ // use the cell map to determine which cell is in which column. void nsTableFrame::BalanceColumnWidths(nsIPresContext* aPresContext, const nsHTMLReflowState& aReflowState, const nsSize& aMaxSize, nsSize* aMaxElementSize) { NS_ASSERTION(nsnull==mPrevInFlow, "never ever call me on a continuing frame!"); nsTableCellMap* cellMap = GetCellMap(); if (!cellMap) { NS_ASSERTION(PR_FALSE, "never ever call me until the cell map is built!"); return; } PRInt32 numCols = cellMap->GetColCount(); if (numCols>mColumnWidthsLength) { PRInt32 priorColumnWidthsLength=mColumnWidthsLength; if (0 == priorColumnWidthsLength) { mColumnWidthsLength = numCols; } else { while (numCols>mColumnWidthsLength) mColumnWidthsLength += kColumnWidthIncrement; } PRInt32 * newColumnWidthsArray = new PRInt32[mColumnWidthsLength]; nsCRT::memset (newColumnWidthsArray, 0, mColumnWidthsLength*sizeof(PRInt32)); if (mColumnWidths) { nsCRT::memcpy (newColumnWidthsArray, mColumnWidths, priorColumnWidthsLength*sizeof(PRInt32)); delete [] mColumnWidths; } mColumnWidths = newColumnWidthsArray; } // need to figure out the overall table width constraint // default case, get 100% of available space PRInt32 maxWidth = CalcBorderBoxWidth(aReflowState); // based on the compatibility mode, create a table layout strategy nscoord boxWidth = CalcBorderBoxWidth(aReflowState); if (nsnull == mTableLayoutStrategy) { nsCompatibility mode; aPresContext->GetCompatibilityMode(&mode); if (!IsAutoLayout(&aReflowState)) mTableLayoutStrategy = new FixedTableLayoutStrategy(this); else mTableLayoutStrategy = new BasicTableLayoutStrategy(this, eCompatibility_NavQuirks == mode); mTableLayoutStrategy->Initialize(aPresContext, aMaxElementSize, boxWidth, aReflowState); mBits.mColumnWidthsValid=PR_TRUE; } // fixed-layout tables need to reinitialize the layout strategy. When there are scroll bars // reflow gets called twice and the 2nd time has the correct space available. else if (!IsAutoLayout(&aReflowState)) { mTableLayoutStrategy->Initialize(aPresContext, aMaxElementSize, boxWidth, aReflowState); } mTableLayoutStrategy->BalanceColumnWidths(aPresContext, mStyleContext, aReflowState, maxWidth); //Dump(PR_TRUE, PR_TRUE); mBits.mColumnWidthsSet=PR_TRUE; // if collapsing borders, compute the top and bottom edges now that we have column widths if ((NS_STYLE_BORDER_COLLAPSE == GetBorderCollapseStyle()) && mBorderCollapser) { mBorderCollapser->ComputeHorizontalBorders(aPresContext, 0, cellMap->GetRowCount()-1); } } /** sum the width of each column add in table insets set rect */ void nsTableFrame::SetTableWidth(nsIPresContext* aPresContext, const nsHTMLReflowState& aReflowState) { NS_ASSERTION(nsnull==mPrevInFlow, "never ever call me on a continuing frame!"); nsTableCellMap* cellMap = GetCellMap(); if (!cellMap) { NS_ASSERTION(PR_FALSE, "never ever call me until the cell map is built!"); return; } nscoord cellSpacing = GetCellSpacingX(); PRInt32 tableWidth = 0; PRInt32 numCols = GetColCount(); for (PRInt32 colIndex = 0; colIndex < numCols; colIndex++) { nscoord totalColWidth = mColumnWidths[colIndex]; if (GetNumCellsOriginatingInCol(colIndex) > 0) { // skip degenerate cols totalColWidth += cellSpacing; // add cell spacing to left of col } tableWidth += totalColWidth; } if (numCols > 0) { tableWidth += cellSpacing; // add last cellspacing } else if (0 == tableWidth) { nsRect tableRect = mRect; tableRect.width = 0; SetRect(aPresContext, tableRect); return; } // Compute the insets (sum of border and padding) nsMargin borderPadding; GetTableBorder (borderPadding); // this gets the max border value at every edge borderPadding += aReflowState.mComputedPadding; nscoord rightInset = borderPadding.right; nscoord leftInset = borderPadding.left; tableWidth += (leftInset + rightInset); nsRect tableSize = mRect; tableSize.width = tableWidth; SetRect(aPresContext, tableSize); } // XXX percentage based margin/border/padding nscoord GetVerticalMarginBorderPadding(nsIFrame* aFrame, const nsIID& aIID) { nscoord result = 0; if (!aFrame) { return result; } nsCOMPtr iContent; nsresult rv = aFrame->GetContent(getter_AddRefs(iContent)); if (NS_SUCCEEDED(rv)) { nsIHTMLContent* htmlContent = nsnull; rv = iContent->QueryInterface(aIID, (void **)&htmlContent); if (htmlContent && NS_SUCCEEDED(rv)) { nsCOMPtr styleContext; aFrame->GetStyleContext(getter_AddRefs(styleContext)); const nsStyleSpacing* spacing = (const nsStyleSpacing*)styleContext->GetStyleData(eStyleStruct_Spacing); nsMargin margin(0,0,0,0); if (spacing->GetMargin(margin)) { result += margin.top + margin.bottom; } if (spacing->GetBorderPadding(margin)) { result += margin.top + margin.bottom; } NS_RELEASE(htmlContent); } } return result; } /* Get the height of the nearest ancestor of this table which has a height other than * auto, except when there is an ancestor which is a table and that table does not have * a coord height. It can be the case that the nearest such ancestor is a scroll frame * or viewport frame; this provides backwards compatibility with Nav4.X and IE. */ nscoord nsTableFrame::GetEffectiveContainerHeight(const nsHTMLReflowState& aReflowState) { nsIFrame* lastArea = nsnull; nsIFrame* lastBlock = nsnull; nsIAtom* frameType = nsnull; nscoord result = -1; const nsHTMLReflowState* rs = &aReflowState; while (rs) { const nsStyleDisplay* display; rs->frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct *&)display); if (NS_STYLE_DISPLAY_TABLE == display->mDisplay) { const nsStylePosition* position; rs->frame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct *&)position); nsStyleUnit unit = position->mHeight.GetUnit(); if ((eStyleUnit_Null == unit) || (eStyleUnit_Auto == unit)) { result = 0; break; } } if (NS_AUTOHEIGHT != rs->mComputedHeight) { result = rs->mComputedHeight; // if we get to the scroll frame or viewport frame, then subtract out // margin/border/padding for the HTML and BODY elements rs->frame->GetFrameType(&frameType); if ((nsLayoutAtoms::viewportFrame == frameType) || (nsLayoutAtoms::scrollFrame == frameType)) { result -= GetVerticalMarginBorderPadding(lastArea, kIHTMLElementIID); result -= GetVerticalMarginBorderPadding(lastBlock, kIBodyElementIID); } NS_IF_RELEASE(frameType); break; } // keep track of the area and block frame on the way up because they could // be the HTML and BODY elements rs->frame->GetFrameType(&frameType); if (nsLayoutAtoms::areaFrame == frameType) { lastArea = rs->frame; } else if (nsLayoutAtoms::blockFrame == frameType) { lastBlock = rs->frame; } NS_IF_RELEASE(frameType); // XXX: evil cast! rs = (nsHTMLReflowState *)(rs->parentReflowState); } NS_ASSERTION(-1 != result, "bad state: no constrained height in reflow chain"); return result; } /** get the table height attribute if it is auto, table height = SUM(height of rowgroups) else if (resolved table height attribute > SUM(height of rowgroups)) proportionately distribute extra height to each row we assume we are passed in the default table height==the sum of the heights of the table's rowgroups in aDesiredSize.height. */ void nsTableFrame::DistributeSpaceToCells(nsIPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nsIFrame* aRowGroupFrame) { // now that all of the rows have been resized, resize the cells nsTableRowGroupFrame* rowGroupFrame = (nsTableRowGroupFrame*)aRowGroupFrame; nsIFrame * rowFrame = rowGroupFrame->GetFirstFrame(); while (nsnull!=rowFrame) { const nsStyleDisplay *rowDisplay; rowFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)rowDisplay)); if (NS_STYLE_DISPLAY_TABLE_ROW == rowDisplay->mDisplay) { ((nsTableRowFrame *)rowFrame)->DidResize(aPresContext, aReflowState); } // The calling function, DistributeSpaceToRows, takes care of the recursive row // group descent, which is why there's no NS_STYLE_DISPLAY_TABLE_ROW_GROUP case // here. rowGroupFrame->GetNextFrame(rowFrame, &rowFrame); } } void nsTableFrame::DistributeSpaceToRows(nsIPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nsIFrame* aRowGroupFrame, const nscoord& aSumOfRowHeights, const nscoord& aExcess, const nsStyleTable* aTableStyle, nscoord& aExcessForRowGroup, nscoord& aRowGroupYPos) { // the rows in rowGroupFrame need to be expanded by rowHeightDelta[i] // and the rowgroup itself needs to be expanded by SUM(row height deltas) nsTableRowGroupFrame* rowGroupFrame = (nsTableRowGroupFrame*)aRowGroupFrame; nsIFrame * rowFrame = rowGroupFrame->GetFirstFrame(); nscoord y = 0; while (nsnull!=rowFrame) { const nsStyleDisplay *rowDisplay; rowFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)rowDisplay)); if (NS_STYLE_DISPLAY_TABLE_ROW_GROUP == rowDisplay->mDisplay) { DistributeSpaceToRows(aPresContext, aReflowState, rowFrame, aSumOfRowHeights, aExcess, aTableStyle, aExcessForRowGroup, y); } else if (NS_STYLE_DISPLAY_TABLE_ROW == rowDisplay->mDisplay) { // the row needs to be expanded by the proportion this row contributed to the original height nsRect rowRect; rowFrame->GetRect(rowRect); float percent = ((float)(rowRect.height)) / ((float)(aSumOfRowHeights)); nscoord excessForRow = NSToCoordRound((float)aExcess*percent); if (rowGroupFrame->RowsDesireExcessSpace()) { nsRect newRowRect(rowRect.x, y, rowRect.width, excessForRow+rowRect.height); rowFrame->SetRect(aPresContext, newRowRect); if ((NS_STYLE_BORDER_COLLAPSE == GetBorderCollapseStyle()) && mBorderCollapser) { PRInt32 rowIndex = ((nsTableRowFrame*)rowFrame)->GetRowIndex(); mBorderCollapser->SetBorderEdgeLength(NS_SIDE_LEFT, rowIndex, newRowRect.height); mBorderCollapser->SetBorderEdgeLength(NS_SIDE_RIGHT, rowIndex, newRowRect.height); } // better if this were part of an overloaded row::SetRect y += excessForRow+rowRect.height; } aExcessForRowGroup += excessForRow; } else { nsRect rowRect; rowFrame->GetRect(rowRect); y += rowRect.height; } rowGroupFrame->GetNextFrame(rowFrame, &rowFrame); } nsRect rowGroupRect; aRowGroupFrame->GetRect(rowGroupRect); if (rowGroupFrame->RowGroupDesiresExcessSpace()) { nsRect newRowGroupRect(rowGroupRect.x, aRowGroupYPos, rowGroupRect.width, aExcessForRowGroup+rowGroupRect.height); aRowGroupFrame->SetRect(aPresContext, newRowGroupRect); aRowGroupYPos += aExcessForRowGroup + rowGroupRect.height; } else aRowGroupYPos += rowGroupRect.height; DistributeSpaceToCells(aPresContext, aReflowState, aRowGroupFrame); } nscoord nsTableFrame::ComputeDesiredHeight(nsIPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nscoord aDefaultHeight) { nsTableCellMap* cellMap = GetCellMap(); if (!cellMap) { NS_ASSERTION(PR_FALSE, "never ever call me until the cell map is built!"); return 0; } nscoord result = aDefaultHeight; nscoord tableSpecifiedHeight = CalcBorderBoxHeight(aReflowState, PR_TRUE); if ((tableSpecifiedHeight > 0) && (tableSpecifiedHeight != NS_UNCONSTRAINEDSIZE)) { if (tableSpecifiedHeight > aDefaultHeight) { result = tableSpecifiedHeight; if (NS_UNCONSTRAINEDSIZE != aReflowState.availableWidth) { // proportionately distribute the excess height to each row. Note that we // don't need to do this if it's an unconstrained reflow nscoord excess = tableSpecifiedHeight - aDefaultHeight; nscoord sumOfRowHeights = 0; nscoord rowGroupYPos = 0; nsIFrame* childFrame = mFrames.FirstChild(); nsIFrame* firstRowGroupFrame = nsnull; while (nsnull != childFrame) { const nsStyleDisplay* childDisplay; childFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)childDisplay)); if (IsRowGroup(childDisplay->mDisplay)) { if (((nsTableRowGroupFrame*)childFrame)->RowGroupReceivesExcessSpace()) { ((nsTableRowGroupFrame*)childFrame)->GetHeightOfRows(aPresContext, sumOfRowHeights); } if (!firstRowGroupFrame) { nsMargin borderPadding = aReflowState.mComputedBorderPadding; rowGroupYPos = borderPadding.top; firstRowGroupFrame = childFrame; } } childFrame->GetNextSibling(&childFrame); } childFrame = mFrames.FirstChild(); while (childFrame) { const nsStyleDisplay* childDisplay; childFrame->GetStyleData(eStyleStruct_Display, ((const nsStyleStruct *&)childDisplay)); if (IsRowGroup(childDisplay->mDisplay)) { if (((nsTableRowGroupFrame*)childFrame)->RowGroupReceivesExcessSpace()) { nscoord excessForGroup = 0; const nsStyleTable* tableStyle; GetStyleData(eStyleStruct_Table, (const nsStyleStruct *&)tableStyle); DistributeSpaceToRows(aPresContext, aReflowState, childFrame, sumOfRowHeights, excess, tableStyle, excessForGroup, rowGroupYPos); // Make sure child views are properly positioned nsIView* view; childFrame->GetView(aPresContext, &view); if (view) { nsContainerFrame::PositionFrameView(aPresContext, childFrame, view); } else { nsContainerFrame::PositionChildViews(aPresContext, childFrame); } } else { nsRect rowGroupRect; childFrame->GetRect(rowGroupRect); rowGroupYPos += rowGroupRect.height; } } childFrame->GetNextSibling(&childFrame); } } } } return result; } void nsTableFrame::AdjustColumnsForCOLSAttribute() { // XXX this is not right, #if 0 nsTableCellMap *cellMap = GetCellMap(); NS_ASSERTION(nsnull!=cellMap, "bad cell map"); // any specified-width column turns off COLS attribute nsStyleTable* tableStyle = (nsStyleTable *)mStyleContext->GetMutableStyleData(eStyleStruct_Table); if (tableStyle->mCols != NS_STYLE_TABLE_COLS_NONE) { PRInt32 numCols = cellMap->GetColCount(); PRInt32 numRows = cellMap->GetRowCount(); for (PRInt32 rowIndex=0; rowIndexGetCellFrameAt(rowIndex, colIndex); // get the cell style info const nsStylePosition* cellPosition; if (nsnull!=cellFrame) { cellFrame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct *&)cellPosition); if ((eStyleUnit_Coord == cellPosition->mWidth.GetUnit()) || (eStyleUnit_Percent==cellPosition->mWidth.GetUnit())) { tableStyle->mCols = NS_STYLE_TABLE_COLS_NONE; break; } } } } } #endif } NS_METHOD nsTableFrame::GetColumnFrame(PRInt32 aColIndex, nsTableColFrame *&aColFrame) { aColFrame = (nsTableColFrame *)mColFrames.ElementAt(aColIndex); return NS_OK; } PRBool nsTableFrame::IsColumnWidthsSet() { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); NS_ASSERTION(nsnull!=firstInFlow, "illegal state -- no first in flow"); return (PRBool)firstInFlow->mBits.mColumnWidthsSet; } PRBool nsTableFrame::ColumnsCanBeInvalidatedBy(nsStyleCoord* aPrevStyleWidth, const nsTableCellFrame& aCellFrame) const { if (mTableLayoutStrategy) { return mTableLayoutStrategy->ColumnsCanBeInvalidatedBy(aPrevStyleWidth, aCellFrame); } return PR_FALSE; } PRBool nsTableFrame::ColumnsCanBeInvalidatedBy(const nsTableCellFrame& aCellFrame, PRBool aConsiderMinWidth) const { if (mTableLayoutStrategy) { return mTableLayoutStrategy->ColumnsCanBeInvalidatedBy(aCellFrame, aConsiderMinWidth); } return PR_FALSE; } PRBool nsTableFrame::ColumnsAreValidFor(const nsTableCellFrame& aCellFrame, nscoord aPrevCellMin, nscoord aPrevCellDes) const { if (mTableLayoutStrategy) { return mTableLayoutStrategy->ColumnsAreValidFor(aCellFrame, aPrevCellMin, aPrevCellDes); } return PR_FALSE; } // XXX This could be more incremental void nsTableFrame::InvalidateColumnWidths() { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); NS_ASSERTION(nsnull!=firstInFlow, "illegal state -- no first in flow"); firstInFlow->mBits.mColumnWidthsValid=PR_FALSE; } PRBool nsTableFrame::IsColumnWidthsValid() const { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); NS_ASSERTION(nsnull!=firstInFlow, "illegal state -- no first in flow"); return (PRBool)firstInFlow->mBits.mColumnWidthsValid; } PRBool nsTableFrame::IsFirstPassValid() const { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); NS_ASSERTION(nsnull!=firstInFlow, "illegal state -- no first in flow"); return (PRBool)firstInFlow->mBits.mFirstPassValid; } void nsTableFrame::InvalidateFirstPassCache() { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); NS_ASSERTION(nsnull!=firstInFlow, "illegal state -- no first in flow"); firstInFlow->mBits.mFirstPassValid=PR_FALSE; } PRBool nsTableFrame::IsMaximumWidthValid() const { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); NS_ASSERTION(nsnull!=firstInFlow, "illegal state -- no first in flow"); return (PRBool)firstInFlow->mBits.mMaximumWidthValid; } void nsTableFrame::InvalidateMaximumWidth() { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); NS_ASSERTION(nsnull!=firstInFlow, "illegal state -- no first in flow"); firstInFlow->mBits.mMaximumWidthValid=PR_FALSE; } PRInt32 nsTableFrame::GetColumnWidth(PRInt32 aColIndex) { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); NS_ASSERTION(nsnull!=firstInFlow, "illegal state -- no first in flow"); PRInt32 result = 0; if (this!=firstInFlow) result = firstInFlow->GetColumnWidth(aColIndex); else { // Because we lazily allocate the column widths when balancing the // columns, it may not be allocated yet. For example, if this is // an incremental reflow. That's okay, just return 0 for the column // width #ifdef NS_DEBUG nsTableCellMap* cellMap = GetCellMap(); if (!cellMap) { NS_ASSERTION(PR_FALSE, "no cell map"); return 0; } PRInt32 numCols = cellMap->GetColCount(); NS_ASSERTION (numCols > aColIndex, "bad arg, col index out of bounds"); #endif if (nsnull!=mColumnWidths) result = mColumnWidths[aColIndex]; } return result; } void nsTableFrame::SetColumnWidth(PRInt32 aColIndex, nscoord aWidth) { nsTableFrame * firstInFlow = (nsTableFrame *)GetFirstInFlow(); NS_ASSERTION(nsnull!=firstInFlow, "illegal state -- no first in flow"); if (this!=firstInFlow) firstInFlow->SetColumnWidth(aColIndex, aWidth); else { // Note: in the case of incremental reflow sometimes the table layout // strategy will call to set a column width before we've allocated the // column width array if (!mColumnWidths) { mColumnWidthsLength = mCellMap->GetColCount(); // mCellMap is valid since first inflow mColumnWidths = new PRInt32[mColumnWidthsLength]; nsCRT::memset (mColumnWidths, 0, mColumnWidthsLength*sizeof(PRInt32)); } if (nsnull!=mColumnWidths && aColIndexGetStyleData(eStyleStruct_Color); // Look until we find a style context with a NON-transparent background color while (styleContext) { if ((colorData->mBackgroundFlags & NS_STYLE_BG_COLOR_TRANSPARENT)!=0) { nsIStyleContext* temp = styleContext; styleContext = styleContext->GetParent(); if (temp != mStyleContext) NS_RELEASE(temp); colorData = (const nsStyleColor*)styleContext->GetStyleData(eStyleStruct_Color); } else { break; } } // Yaahoo, we found a style context which has a background color nscolor borderColor = 0xFFC0C0C0; if (styleContext != nsnull) { borderColor = colorData->mBackgroundColor; if (styleContext != mStyleContext) NS_RELEASE(styleContext); } // if the border color is white, then shift to grey if (borderColor == 0xFFFFFFFF) borderColor = 0xFFC0C0C0; aSpacingStyle.SetBorderColor(NS_SIDE_TOP, borderColor); aSpacingStyle.SetBorderColor(NS_SIDE_LEFT, borderColor); aSpacingStyle.SetBorderColor(NS_SIDE_BOTTOM, borderColor); aSpacingStyle.SetBorderColor(NS_SIDE_RIGHT, borderColor); } PRBool nsTableFrame::ConvertToPixelValue(nsHTMLValue& aValue, PRInt32 aDefault, PRInt32& aResult) { if (aValue.GetUnit() == eHTMLUnit_Pixel) aResult = aValue.GetPixelValue(); else if (aValue.GetUnit() == eHTMLUnit_Empty) aResult = aDefault; else { NS_ERROR("Unit must be pixel or empty"); return PR_FALSE; } return PR_TRUE; } void nsTableFrame::MapBorderMarginPadding(nsIPresContext* aPresContext) { #if 0 // Check to see if the table has either cell padding or // Cell spacing defined for the table. If true, then // this setting overrides any specific border, margin or // padding information in the cell. If these attributes // are not defined, the the cells attributes are used nsHTMLValue padding_value; nsHTMLValue spacing_value; nsHTMLValue border_value; nsresult border_result; nscoord padding = 0; nscoord spacing = 0; nscoord border = 1; float p2t = aPresContext->GetPixelsToTwips(); nsIHTMLContent* table = (nsIHTMLContent*)mContent; NS_ASSERTION(table,"Table Must not be null"); if (!table) return; nsStyleSpacing* spacingData = (nsStyleSpacing*)mStyleContext->GetMutableStyleData(eStyleStruct_Spacing); border_result = table->GetAttribute(nsHTMLAtoms::border,border_value); if (border_result == NS_CONTENT_ATTR_HAS_VALUE) { PRInt32 intValue = 0; if (ConvertToPixelValue(border_value,1,intValue)) //XXX this is busted if this code is ever used again. MMP border = NSIntPixelsToTwips(intValue, p2t); } MapHTMLBorderStyle(*spacingData,border); #endif } nscoord CalcPercentPadding(nscoord aBasis, nsStyleCoord aStyleCoord) { float percent = (NS_UNCONSTRAINEDSIZE == aBasis) ? 0 : aStyleCoord.GetPercentValue(); return NSToCoordRound(((float)aBasis) * percent); } void GetPaddingFor(const nsSize& aBasis, const nsStyleSpacing& aSpacing, nsMargin& aPadding) { nsStyleCoord styleCoord; aSpacing.mPadding.GetTop(styleCoord); if (eStyleUnit_Percent == aSpacing.mPadding.GetTopUnit()) { aPadding.top = CalcPercentPadding(aBasis.height, styleCoord); } else if (eStyleUnit_Coord == aSpacing.mPadding.GetTopUnit()) { aPadding.top = styleCoord.GetCoordValue(); } aSpacing.mPadding.GetRight(styleCoord); if (eStyleUnit_Percent == aSpacing.mPadding.GetRightUnit()) { aPadding.right = CalcPercentPadding(aBasis.width, styleCoord); } else if (eStyleUnit_Coord == aSpacing.mPadding.GetTopUnit()) { aPadding.right = styleCoord.GetCoordValue(); } aSpacing.mPadding.GetBottom(styleCoord); if (eStyleUnit_Percent == aSpacing.mPadding.GetBottomUnit()) { aPadding.bottom = CalcPercentPadding(aBasis.height, styleCoord); } else if (eStyleUnit_Coord == aSpacing.mPadding.GetTopUnit()) { aPadding.bottom = styleCoord.GetCoordValue(); } aSpacing.mPadding.GetLeft(styleCoord); if (eStyleUnit_Percent == aSpacing.mPadding.GetLeftUnit()) { aPadding.left = CalcPercentPadding(aBasis.width, styleCoord); } else if (eStyleUnit_Coord == aSpacing.mPadding.GetTopUnit()) { aPadding.left = styleCoord.GetCoordValue(); } } nsMargin nsTableFrame::GetPadding(const nsHTMLReflowState& aReflowState, const nsTableCellFrame* aCellFrame) { const nsStyleSpacing* spacing; aCellFrame->GetStyleData(eStyleStruct_Spacing,(const nsStyleStruct *&)spacing); nsMargin padding(0,0,0,0); if (!spacing->GetPadding(padding)) { const nsHTMLReflowState* parentRS = aReflowState.parentReflowState; while (parentRS) { if (parentRS->frame) { nsCOMPtr frameType; parentRS->frame->GetFrameType(getter_AddRefs(frameType)); if (nsLayoutAtoms::tableFrame == frameType.get()) { nsSize basis(parentRS->mComputedWidth, parentRS->mComputedHeight); GetPaddingFor(basis, *spacing, padding); break; } } parentRS = parentRS->parentReflowState; } } return padding; } nsMargin nsTableFrame::GetPadding(const nsSize& aBasis, const nsTableCellFrame* aCellFrame) { const nsStyleSpacing* spacing; aCellFrame->GetStyleData(eStyleStruct_Spacing,(const nsStyleStruct *&)spacing); nsMargin padding(0,0,0,0); if (!spacing->GetPadding(padding)) { GetPaddingFor(aBasis, *spacing, padding); } return padding; } NS_METHOD nsTableFrame::GetCellMarginData(nsTableCellFrame* aKidFrame, nsMargin& aMargin) { nsresult result = NS_ERROR_NOT_INITIALIZED; if (aKidFrame) { nscoord spacingX = GetCellSpacingX(); nscoord spacingY = GetCellSpacingY(); PRInt32 rowIndex, colIndex; aKidFrame->GetRowIndex(rowIndex); aKidFrame->GetColIndex(colIndex); // left/top margins only apply if the cell is a left/top border cell // there used to be more complicated logic (rev 3.326) dealing with rowspans // but failure to produce reasonable tests cases does not justify it. aMargin.left = (0 == colIndex) ? spacingX : 0; aMargin.top = (0 == rowIndex) ? spacingY : 0; aMargin.right = spacingX; aMargin.bottom = spacingY; result = NS_OK; } return result; } //XXX: ok, this looks dumb now. but in a very short time this will get filled in void nsTableFrame::GetTableBorder(nsMargin &aBorder) { if (NS_STYLE_BORDER_COLLAPSE == GetBorderCollapseStyle()) { mBorderCollapser->GetBorder(aBorder); } else { const nsStyleSpacing* spacing = (const nsStyleSpacing*)mStyleContext->GetStyleData(eStyleStruct_Spacing); spacing->GetBorder(aBorder); } } /* now I need to actually use gettableborderat, instead of assuming that the table border is homogenous across rows and columns. in tableFrame, LayoutStrategy, and cellFrame, maybe rowFrame need something similar for cell (for those with spans?) */ void nsTableFrame::GetTableBorderAt(PRInt32 aRowIndex, PRInt32 aColIndex, nsMargin& aBorder) { if (NS_STYLE_BORDER_COLLAPSE==GetBorderCollapseStyle()) { if (mBorderCollapser) { mBorderCollapser->GetBorderAt(aRowIndex, aColIndex, aBorder); } } else { const nsStyleSpacing* spacing = (const nsStyleSpacing*)mStyleContext->GetStyleData(eStyleStruct_Spacing); spacing->GetBorder(aBorder); } } void nsTableFrame::SetBorderEdgeLength(PRUint8 aSide, PRInt32 aIndex, nscoord aLength) { if (mBorderCollapser) { mBorderCollapser->SetBorderEdgeLength(aSide, aIndex, aLength); } } void nsTableFrame::GetTableBorderForRowGroup(nsTableRowGroupFrame* aRowGroupFrame, nsMargin& aBorder) { aBorder.SizeTo(0,0,0,0); if (aRowGroupFrame) { if (NS_STYLE_BORDER_COLLAPSE == GetBorderCollapseStyle()) { if (mBorderCollapser) { PRInt32 rowCount; aRowGroupFrame->GetRowCount(rowCount); nsTableCellMap* cellMap = GetCellMap(); //PRInt32 colCount = cellMap->GetColCount(); mBorderCollapser->GetMaxBorder(aRowGroupFrame->GetStartRowIndex(), rowCount - 1, 0, cellMap->GetColCount() - 1, aBorder); } } else { GetTableBorder (aBorder); } } } PRUint8 nsTableFrame::GetBorderCollapseStyle() { const nsStyleTable* tableStyle; GetStyleData(eStyleStruct_Table, (const nsStyleStruct *&)tableStyle); return tableStyle->mBorderCollapse; } // XXX: could cache this. But be sure to check style changes if you do! nscoord nsTableFrame::GetCellSpacingX() { const nsStyleTable* tableStyle; GetStyleData(eStyleStruct_Table, (const nsStyleStruct *&)tableStyle); nscoord cellSpacing = 0; PRUint8 borderCollapseStyle = GetBorderCollapseStyle(); if (NS_STYLE_BORDER_COLLAPSE != borderCollapseStyle) { if (tableStyle->mBorderSpacingX.GetUnit() == eStyleUnit_Coord) { cellSpacing = tableStyle->mBorderSpacingX.GetCoordValue(); } } return cellSpacing; } // XXX: could cache this. But be sure to check style changes if you do! nscoord nsTableFrame::GetCellSpacingY() { const nsStyleTable* tableStyle; GetStyleData(eStyleStruct_Table, (const nsStyleStruct *&)tableStyle); nscoord cellSpacing = 0; PRUint8 borderCollapseStyle = GetBorderCollapseStyle(); if (NS_STYLE_BORDER_COLLAPSE != borderCollapseStyle) { if (tableStyle->mBorderSpacingY.GetUnit() == eStyleUnit_Coord) { cellSpacing = tableStyle->mBorderSpacingY.GetCoordValue(); } } return cellSpacing; } // Get the cellpadding defined on the table. Each cell can override this with style nscoord nsTableFrame::GetCellPadding() { const nsStyleTable* tableStyle; GetStyleData(eStyleStruct_Table, (const nsStyleStruct *&)tableStyle); nscoord cellPadding = -1; if (tableStyle->mCellPadding.GetUnit() == eStyleUnit_Coord) { cellPadding = tableStyle->mCellPadding.GetCoordValue(); } return cellPadding; } /* ----- global methods ----- */ nsresult NS_NewTableFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame) { NS_PRECONDITION(aNewFrame, "null OUT ptr"); if (nsnull == aNewFrame) { return NS_ERROR_NULL_POINTER; } nsTableFrame* it = new (aPresShell) nsTableFrame; if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } *aNewFrame = it; return NS_OK; } NS_METHOD nsTableFrame::GetTableFrame(nsIFrame *aSourceFrame, nsTableFrame *& aTableFrame) { nsresult rv = NS_ERROR_UNEXPECTED; // the value returned aTableFrame = nsnull; // initialize out-param nsIFrame *parentFrame=nsnull; if (nsnull!=aSourceFrame) { // "result" is the result of intermediate calls, not the result we return from this method nsresult result = aSourceFrame->GetParent((nsIFrame **)&parentFrame); while ((NS_OK==result) && (nsnull!=parentFrame)) { const nsStyleDisplay *display; parentFrame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct *&)display); if (NS_STYLE_DISPLAY_TABLE == display->mDisplay) { aTableFrame = (nsTableFrame *)parentFrame; rv = NS_OK; // only set if we found the table frame break; } result = parentFrame->GetParent((nsIFrame **)&parentFrame); } } NS_POSTCONDITION(nsnull!=aTableFrame, "unable to find table parent. aTableFrame null."); NS_POSTCONDITION(NS_OK==rv, "unable to find table parent. result!=NS_OK"); return rv; } /* helper method for determining if this is a nested table or not */ // aReflowState must be the reflow state for this inner table frame, should have an assertion here for that PRBool nsTableFrame::IsNested(const nsHTMLReflowState& aReflowState, const nsStylePosition *& aPosition) const { PRBool result = PR_FALSE; // Walk up the reflow state chain until we find a cell or the root const nsHTMLReflowState* rs = aReflowState.parentReflowState; // this is for the outer frame if (rs) rs = rs->parentReflowState; // and this is the parent of the outer frame while (nsnull != rs) { const nsStyleDisplay *display; rs->frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct *&)display); if (NS_STYLE_DISPLAY_TABLE==display->mDisplay) { result = PR_TRUE; rs->frame->GetStyleData(eStyleStruct_Position, ((const nsStyleStruct *&)aPosition)); break; } rs = rs->parentReflowState; } return result; } PRBool nsTableFrame::IsAutoWidth() { PRBool isAuto = PR_TRUE; // the default nsStylePosition* tablePosition = (nsStylePosition*)mStyleContext->GetStyleData(eStyleStruct_Position); switch (tablePosition->mWidth.GetUnit()) { case eStyleUnit_Auto: // specified auto width case eStyleUnit_Proportional: // illegal for table, so ignored break; case eStyleUnit_Inherit: // get width of parent and see if it is a specified value or not // XXX for now, just return true break; case eStyleUnit_Coord: case eStyleUnit_Percent: isAuto = PR_FALSE; break; default: break; } return isAuto; } nscoord nsTableFrame::CalcBorderBoxWidth(const nsHTMLReflowState& aState) { nscoord width = aState.mComputedWidth; if (eStyleUnit_Auto == aState.mStylePosition->mWidth.GetUnit()) { if (0 == width) { width = NS_UNCONSTRAINEDSIZE; } if (NS_UNCONSTRAINEDSIZE != aState.availableWidth) { nsMargin margin(0,0,0,0); aState.mStyleSpacing->GetMargin(margin); width = aState.availableWidth - margin.left - margin.right; } } else if (width != NS_UNCONSTRAINEDSIZE) { nsMargin borderPadding(0,0,0,0); aState.mStyleSpacing->GetBorderPadding(borderPadding); width += borderPadding.left + borderPadding.right; } width = PR_MAX(width, 0); return width; } nscoord nsTableFrame::CalcBorderBoxHeight(const nsHTMLReflowState& aState, PRBool aDoNavHack) { nscoord height = aState.mComputedHeight; PRBool isAutoHeight = PR_FALSE; PRBool isPercentHack = PR_FALSE; if (eStyleUnit_Auto == aState.mStylePosition->mHeight.GetUnit()) { isAutoHeight = PR_TRUE; } else if (((0 == height) || (NS_UNCONSTRAINEDSIZE == height)) && aDoNavHack && (eStyleUnit_Percent == aState.mStylePosition->mHeight.GetUnit())) { nsIAtom* frameType; aState.frame->GetFrameType(&frameType); if (nsLayoutAtoms::tableFrame == frameType) { float percent = aState.mStylePosition->mHeight.GetPercentValue(); nscoord parentHeight = ((nsTableFrame*)aState.frame)->GetEffectiveContainerHeight(aState); if ((NS_UNCONSTRAINEDSIZE != parentHeight) && (0 != parentHeight)) { // css box-sizing not supported for this Nav hack height = NSToCoordRound((float)parentHeight * percent); isPercentHack = PR_TRUE; } } NS_IF_RELEASE(frameType); } height = PR_MAX(height, 0); if ((height != NS_UNCONSTRAINEDSIZE) && !isAutoHeight && !isPercentHack) { nsMargin borderPadding(0,0,0,0); aState.mStyleSpacing->GetBorderPadding(borderPadding); height += borderPadding.top + borderPadding.bottom; } return height; } nscoord nsTableFrame::GetMinCaptionWidth() { nsIFrame *outerTableFrame=nsnull; GetParent(&outerTableFrame); return (((nsTableOuterFrame *)outerTableFrame)->GetMinCaptionWidth()); } /** return the minimum width of the table. Return 0 if the min width is unknown. */ nscoord nsTableFrame::GetMinTableContentWidth() { nscoord result = 0; if (nsnull!=mTableLayoutStrategy) result = mTableLayoutStrategy->GetTableMinContentWidth(); return result; } /** return the maximum width of the table. Return 0 if the max width is unknown. */ nscoord nsTableFrame::GetMaxTableContentWidth() { nscoord result = 0; if (nsnull!=mTableLayoutStrategy) result = mTableLayoutStrategy->GetTableMaxContentWidth(); return result; } void nsTableFrame::SetMaxElementSize(nsSize* aMaxElementSize, const nsMargin& aPadding) { if (nsnull!=mTableLayoutStrategy) { mTableLayoutStrategy->SetMaxElementSize(aMaxElementSize, aPadding); } } PRBool nsTableFrame::IsAutoLayout(const nsHTMLReflowState* aReflowState) { const nsStyleTable* tableStyle; GetStyleData(eStyleStruct_Table, (const nsStyleStruct *&)tableStyle); // a fixed table-layout table with an auto width is not considered as such // for purposes of requiring pass1 reflow and assigning a layout strategy if (NS_STYLE_TABLE_LAYOUT_FIXED == tableStyle->mLayoutStrategy) { const nsStylePosition* position; if (aReflowState) { position = aReflowState->mStylePosition; } else { GetStyleData(eStyleStruct_Position, (const nsStyleStruct *&)position); } if (eStyleUnit_Auto != position->mWidth.GetUnit()) { return PR_FALSE; } } return PR_TRUE; } #ifdef DEBUG NS_IMETHODIMP nsTableFrame::GetFrameName(nsString& aResult) const { return MakeFrameName("Table", aResult); } #endif // This assumes that aFrame is a scroll frame if nsTableRowGroupFrame* nsTableFrame::GetRowGroupFrameFor(nsIFrame* aFrame, const nsStyleDisplay* aDisplay) { nsIFrame* result = nsnull; // ok this code looks to see if it is a scrollable frame. If it is it asks it for the scrolled // frame that should be the row group frame. -EDV nsIScrollableFrame* scrollable = nsnull; nsresult rv = aFrame->QueryInterface(kIScrollableFrameIID, (void **)&scrollable); if (NS_SUCCEEDED(rv) && (nsnull != scrollable)) { scrollable->GetScrolledFrame(nsnull, result); } else { result = aFrame; } return (nsTableRowGroupFrame*)result; } PRBool nsTableFrame::IsFinalPass(const nsHTMLReflowState& aState) { return (NS_UNCONSTRAINEDSIZE != aState.availableWidth) || (NS_UNCONSTRAINEDSIZE != aState.availableHeight); } // Find the closet sibling before aPriorChildFrame (including aPriorChildFrame) that // is of type aChildType nsIFrame* nsTableFrame::GetFrameAtOrBefore(nsIPresContext* aPresContext, nsIFrame* aParentFrame, nsIFrame* aPriorChildFrame, nsIAtom* aChildType) { nsIFrame* result = nsnull; if (!aPriorChildFrame) { return result; } nsIAtom* frameType; aPriorChildFrame->GetFrameType(&frameType); if (aChildType == frameType) { NS_RELEASE(frameType); return (nsTableCellFrame*)aPriorChildFrame; } NS_IF_RELEASE(frameType); // aPriorChildFrame is not of type aChildType, so we need start from // the beginnng and find the closest one nsIFrame* childFrame; nsIFrame* lastMatchingFrame = nsnull; aParentFrame->FirstChild(aPresContext, nsnull, &childFrame); while (childFrame != aPriorChildFrame) { childFrame->GetFrameType(&frameType); if (aChildType == frameType) { lastMatchingFrame = childFrame; } NS_IF_RELEASE(frameType); childFrame->GetNextSibling(&childFrame); } return (nsTableCellFrame*)lastMatchingFrame; } void nsTableFrame::DumpRowGroup(nsIPresContext* aPresContext, nsIFrame* aKidFrame) { nsTableRowGroupFrame* rgFrame = GetRowGroupFrame(aKidFrame); if (rgFrame) { nsIFrame* rowFrame; rgFrame->FirstChild(aPresContext, nsnull, &rowFrame); while (rowFrame) { nsIAtom* rowType; rowFrame->GetFrameType(&rowType); if (nsLayoutAtoms::tableRowFrame == rowType) { printf("row(%d)=%p ", ((nsTableRowFrame*)rowFrame)->GetRowIndex(), rowFrame); nsIFrame* cellFrame; rowFrame->FirstChild(aPresContext, nsnull, &cellFrame); while (cellFrame) { nsIAtom* cellType; cellFrame->GetFrameType(&cellType); if (nsLayoutAtoms::tableCellFrame == cellType) { PRInt32 colIndex; ((nsTableCellFrame*)cellFrame)->GetColIndex(colIndex); printf("cell(%d)=%p ", colIndex, cellFrame); } NS_IF_RELEASE(cellType); cellFrame->GetNextSibling(&cellFrame); } printf("\n"); } else { DumpRowGroup(aPresContext, rowFrame); } NS_IF_RELEASE(rowType); rowFrame->GetNextSibling(&rowFrame); } } } void nsTableFrame::Dump(nsIPresContext* aPresContext, PRBool aDumpRows, PRBool aDumpCols, PRBool aDumpCellMap) { printf("***START TABLE DUMP*** \n"); // dump the columns widths array printf("mColWidths="); PRInt32 numCols = GetColCount(); PRInt32 colX; for (colX = 0; colX < numCols; colX++) { printf("%d ", mColumnWidths[colX]); } printf("\n"); if (aDumpRows) { nsIFrame* kidFrame = mFrames.FirstChild(); while (kidFrame) { DumpRowGroup(aPresContext, kidFrame); kidFrame->GetNextSibling(&kidFrame); } } if (aDumpCols) { // output col frame cache printf("\n col frame cache ->"); for (colX = 0; colX < numCols; colX++) { nsTableColFrame* colFrame = (nsTableColFrame *)mColFrames.ElementAt(colX); if (0 == (colX % 8)) { printf("\n"); } printf ("%d=%p ", colX, colFrame); } for (colX = 0; colX < numCols; colX++) { printf("\n"); nsTableColFrame* colFrame = GetColFrame(colX); colFrame->Dump(1); } } if (aDumpCellMap) { nsTableCellMap* cellMap = GetCellMap(); #ifdef NS_DEBUG cellMap->Dump(); #endif } printf(" ***END TABLE DUMP*** \n"); } // nsTableIterator nsTableIterator::nsTableIterator(nsIPresContext* aPresContext, nsIFrame& aSource, nsTableIteration aType) { nsIFrame* firstChild; aSource.FirstChild(aPresContext, nsnull, &firstChild); Init(firstChild, aType); } nsTableIterator::nsTableIterator(nsFrameList& aSource, nsTableIteration aType) { nsIFrame* firstChild = aSource.FirstChild(); Init(firstChild, aType); } void nsTableIterator::Init(nsIFrame* aFirstChild, nsTableIteration aType) { mFirstListChild = aFirstChild; mFirstChild = aFirstChild; mCurrentChild = nsnull; mLeftToRight = (eTableRTL == aType) ? PR_FALSE : PR_TRUE; mCount = -1; if (!mFirstChild) { return; } if (eTableDIR == aType) { nsTableFrame* table = nsnull; nsresult rv = nsTableFrame::GetTableFrame(mFirstChild, table); if (NS_SUCCEEDED(rv) && (table != nsnull)) { const nsStyleDisplay* display; table->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); mLeftToRight = (NS_STYLE_DIRECTION_LTR == display->mDirection); } else { NS_ASSERTION(PR_FALSE, "source of table iterator is not part of a table"); return; } } if (!mLeftToRight) { mCount = 0; nsIFrame* nextChild; mFirstChild->GetNextSibling(&nextChild); while (nsnull != nextChild) { mCount++; mFirstChild = nextChild; nextChild->GetNextSibling(&nextChild); } } } nsIFrame* nsTableIterator::First() { mCurrentChild = mFirstChild; return mCurrentChild; } nsIFrame* nsTableIterator::Next() { if (!mCurrentChild) { return nsnull; } if (mLeftToRight) { mCurrentChild->GetNextSibling(&mCurrentChild); return mCurrentChild; } else { nsIFrame* targetChild = mCurrentChild; mCurrentChild = nsnull; nsIFrame* child = mFirstListChild; while (child && (child != targetChild)) { mCurrentChild = child; child->GetNextSibling(&child); } return mCurrentChild; } } PRBool nsTableIterator::IsLeftToRight() { return mLeftToRight; } PRInt32 nsTableIterator::Count() { if (-1 == mCount) { mCount = 0; nsIFrame* child = mFirstListChild; while (nsnull != child) { mCount++; child->GetNextSibling(&child); } } return mCount; } nsTableCellFrame* nsTableFrame::GetCellInfoAt(PRInt32 aRowX, PRInt32 aColX, PRBool* aOriginates, PRInt32* aColSpan) { nsTableCellMap* cellMap = GetCellMap(); return cellMap->GetCellInfoAt(aRowX, aColX, aOriginates, aColSpan); } /*------------------ nsITableLayout methods ------------------------------*/ NS_IMETHODIMP nsTableFrame::GetCellDataAt(PRInt32 aRowIndex, PRInt32 aColIndex, nsIDOMElement* &aCell, //out params PRInt32& aStartRowIndex, PRInt32& aStartColIndex, PRInt32& aRowSpan, PRInt32& aColSpan, PRInt32& aActualRowSpan, PRInt32& aActualColSpan, PRBool& aIsSelected) { // Initialize out params aCell = nsnull; aStartRowIndex = 0; aStartColIndex = 0; aRowSpan = 0; aColSpan = 0; aIsSelected = PR_FALSE; nsTableCellMap* cellMap = GetCellMap(); if (!cellMap) { return NS_ERROR_NOT_INITIALIZED;} PRBool originates; PRInt32 colSpan; // Is this the "effective" or "html" value? nsTableCellFrame *cellFrame = cellMap->GetCellInfoAt(aRowIndex, aColIndex, &originates, &colSpan); if (!cellFrame) return NS_TABLELAYOUT_CELL_NOT_FOUND; nsresult result= cellFrame->GetRowIndex(aStartRowIndex); if (NS_FAILED(result)) return result; result = cellFrame->GetColIndex(aStartColIndex); if (NS_FAILED(result)) return result; //This returns HTML value, which may be 0 aRowSpan = cellFrame->GetRowSpan(); aColSpan = cellFrame->GetColSpan(); aActualRowSpan = GetEffectiveRowSpan(*cellFrame); aActualColSpan = GetEffectiveColSpan(*cellFrame); result = cellFrame->GetSelected(&aIsSelected); if (NS_FAILED(result)) return result; // do this last, because it addrefs, // and we don't want the caller leaking it on error nsCOMPtrcontent; result = cellFrame->GetContent(getter_AddRefs(content)); if (NS_SUCCEEDED(result) && content) { content->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)(&aCell)); } return result; } NS_IMETHODIMP nsTableFrame::GetTableSize(PRInt32& aRowCount, PRInt32& aColCount) { nsTableCellMap* cellMap = GetCellMap(); // Initialize out params aRowCount = 0; aColCount = 0; if (!cellMap) { return NS_ERROR_NOT_INITIALIZED;} aRowCount = cellMap->GetRowCount(); aColCount = cellMap->GetColCount(); return NS_OK; } /*---------------- end of nsITableLayout implementation ------------------*/ PRInt32 nsTableFrame::GetNumCellsOriginatingInCol(PRInt32 aColIndex) const { nsTableCellMap* cellMap = GetCellMap(); return cellMap->GetNumCellsOriginatingInCol(aColIndex); } #define INDENT_PER_LEVEL 2 void nsTableFrame::DebugGetIndent(const nsIFrame* aFrame, char* aBuf) { PRInt32 numLevels = 0; nsIFrame* parent = nsnull; aFrame->GetParent(&parent); while (parent) { nsIAtom* frameType = nsnull; parent->GetFrameType(&frameType); if ((nsDebugTable::gRflTableOuter && (nsLayoutAtoms::tableOuterFrame == frameType)) || (nsDebugTable::gRflTable && (nsLayoutAtoms::tableFrame == frameType)) || (nsDebugTable::gRflRowGrp && (nsLayoutAtoms::tableRowGroupFrame == frameType)) || (nsDebugTable::gRflRow && (nsLayoutAtoms::tableRowFrame == frameType)) || (nsDebugTable::gRflCell && (nsLayoutAtoms::tableCellFrame == frameType)) || (nsDebugTable::gRflArea && (nsLayoutAtoms::areaFrame == frameType))) { numLevels++; } NS_IF_RELEASE(frameType); parent->GetParent(&parent); } PRInt32 indent = INDENT_PER_LEVEL * numLevels; nsCRT::memset (aBuf, ' ', indent); aBuf[indent] = 0; } void PrettyUC(nscoord aSize, char* aBuf) { if (NS_UNCONSTRAINEDSIZE == aSize) { strcpy(aBuf, "UC"); } else { sprintf(aBuf, "%d", aSize); } } void nsTableFrame::DebugReflow(char* aMessage, const nsIFrame* aFrame, const nsHTMLReflowState* aState, const nsHTMLReflowMetrics* aMetrics, const nsReflowStatus aStatus) { char indent[256]; nsTableFrame::DebugGetIndent(aFrame, indent); printf("%s%s %p ", indent, aMessage, aFrame); char width[32]; char height[32]; if (aState) { PrettyUC(aState->availableWidth, width); PrettyUC(aState->availableHeight, height); printf("rea=%d av=(%s,%s) ", aState->reason, width, height); PrettyUC(aState->mComputedWidth, width); PrettyUC(aState->mComputedHeight, height); printf("comp=(%s,%s) count=%d \n ", width, height, gRflCount); gRflCount++; //if (32 == gRflCount) { // NS_ASSERTION(PR_FALSE, "stop"); //} } if (aMetrics) { if (aState) { printf("%s", indent); } PrettyUC(aMetrics->width, width); PrettyUC(aMetrics->height, height); printf("des=(%s,%s) ", width, height); if (aMetrics->maxElementSize) { PrettyUC(aMetrics->maxElementSize->width, width); PrettyUC(aMetrics->maxElementSize->height, height); printf("maxElem=(%s,%s)", width, height); } if (aMetrics->mFlags & NS_REFLOW_CALC_MAX_WIDTH) { printf("max=%d ", aMetrics->mMaximumWidth); } if (NS_FRAME_COMPLETE != aStatus) { printf("status=%d", aStatus); } printf("\n"); } } PRBool nsTableFrame::RowHasSpanningCells(PRInt32 aRowIndex) { PRBool result = PR_FALSE; nsTableCellMap* cellMap = GetCellMap(); NS_PRECONDITION (cellMap, "bad call, cellMap not yet allocated."); if (cellMap) { result = cellMap->RowHasSpanningCells(aRowIndex); } return result; } PRBool nsTableFrame::RowIsSpannedInto(PRInt32 aRowIndex) { PRBool result = PR_FALSE; nsTableCellMap* cellMap = GetCellMap(); NS_PRECONDITION (cellMap, "bad call, cellMap not yet allocated."); if (cellMap) { result = cellMap->RowIsSpannedInto(aRowIndex); } return result; } PRBool nsTableFrame::ColIsSpannedInto(PRInt32 aColIndex) { PRBool result = PR_FALSE; nsTableCellMap * cellMap = GetCellMap(); NS_PRECONDITION (cellMap, "bad call, cellMap not yet allocated."); if (cellMap) { result = cellMap->ColIsSpannedInto(aColIndex); } return result; } #ifdef DEBUG NS_IMETHODIMP nsTableFrame::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const { if (!aResult) { return NS_ERROR_NULL_POINTER; } PRUint32 sum = sizeof(*this); // Add in the amount of space for the column width array sum += mColumnWidthsLength * sizeof(PRInt32); // Add in size of cell map PRUint32 cellMapSize; mCellMap->SizeOf(aHandler, &cellMapSize); aHandler->AddSize(nsLayoutAtoms::cellMap, cellMapSize); // Add in size of col cache PRUint32 voidArraySize; mColFrames.SizeOf(aHandler, &voidArraySize); sum += voidArraySize - sizeof(mColFrames); // Add in size of table layout strategy PRUint32 strategySize; mTableLayoutStrategy->SizeOf(aHandler, &strategySize); aHandler->AddSize(nsLayoutAtoms::tableStrategy, strategySize); *aResult = sum; return NS_OK; } #endif