/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ #include "nsTableCellFrame.h" #include "nsCellLayoutData.h" #include "nsBodyFrame.h" #include "nsIReflowCommand.h" #include "nsIStyleContext.h" #include "nsStyleConsts.h" #include "nsIPresContext.h" #include "nsIRenderingContext.h" #include "nsCSSRendering.h" #include "nsIContent.h" #include "nsIContentDelegate.h" #include "nsCSSLayout.h" #include "nsHTMLValue.h" #include "nsHTMLAtoms.h" #include "nsHTMLIIDs.h" #include "nsIPtr.h" #include "nsIView.h" // dependancy on content #include "nsHTMLTagContent.h" NS_DEF_PTR(nsIStyleContext); const nsIID kTableCellFrameCID = NS_TABLECELLFRAME_CID; #ifdef NS_DEBUG static PRBool gsDebug = PR_FALSE; static PRBool gsDebugNT = PR_FALSE; //#define NOISY_STYLE //#define NOISY_FLOW #else static const PRBool gsDebug = PR_FALSE; static const PRBool gsDebugNT = PR_FALSE; #endif /** */ nsTableCellFrame::nsTableCellFrame(nsIContent* aContent, nsIFrame* aParentFrame) : nsContainerFrame(aContent, aParentFrame), mCellLayoutData(nsnull) { mRowSpan=1; mColSpan=1; mColIndex=0; mPriorAvailWidth=0; mPriorDesiredSize.width=0; mPriorDesiredSize.height=0; } nsTableCellFrame::~nsTableCellFrame() { if (nsnull!=mCellLayoutData) delete mCellLayoutData; } NS_METHOD nsTableCellFrame::Paint(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect) { const nsStyleDisplay* disp = (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); if (disp->mVisible) { const nsStyleColor* myColor = (const nsStyleColor*)mStyleContext->GetStyleData(eStyleStruct_Color); const nsStyleSpacing* mySpacing = (const nsStyleSpacing*)mStyleContext->GetStyleData(eStyleStruct_Spacing); NS_ASSERTION(nsnull!=myColor, "bad style color"); NS_ASSERTION(nsnull!=mySpacing, "bad style spacing"); nsCSSRendering::PaintBackground(aPresContext, aRenderingContext, this, aDirtyRect, mRect, *myColor); nsCSSRendering::PaintBorder(aPresContext, aRenderingContext, this, aDirtyRect, mRect, *mySpacing, 0); } // for debug... if (nsIFrame::GetShowFrameBorders()) { aRenderingContext.SetColor(NS_RGB(0,128,128)); aRenderingContext.DrawRect(0, 0, mRect.width, mRect.height); } PaintChildren(aPresContext, aRenderingContext, aDirtyRect); return NS_OK; } /** * * Align the cell's child frame within the cell * **/ void nsTableCellFrame::VerticallyAlignChild(nsIPresContext* aPresContext) { const nsStyleSpacing* spacing = (const nsStyleSpacing*)mStyleContext->GetStyleData(eStyleStruct_Spacing); const nsStyleText* textStyle = (const nsStyleText*)mStyleContext->GetStyleData(eStyleStruct_Text); nsMargin borderPadding; spacing->CalcBorderPaddingFor(this, borderPadding); nscoord topInset = borderPadding.top; nscoord bottomInset = borderPadding.bottom; PRUint8 verticalAlignFlags = NS_STYLE_VERTICAL_ALIGN_MIDDLE; if (textStyle->mVerticalAlign.GetUnit() == eStyleUnit_Enumerated) { verticalAlignFlags = textStyle->mVerticalAlign.GetIntValue(); } nscoord height = mRect.height; nsRect kidRect; mFirstChild->GetRect(kidRect); nscoord childHeight = kidRect.height; // Vertically align the child nscoord kidYTop = 0; switch (verticalAlignFlags) { case NS_STYLE_VERTICAL_ALIGN_BASELINE: // Align the child's baseline at the max baseline //kidYTop = aMaxAscent - kidAscent; break; case NS_STYLE_VERTICAL_ALIGN_TOP: // Align the top of the child frame with the top of the box, // minus the top padding kidYTop = topInset; break; case NS_STYLE_VERTICAL_ALIGN_BOTTOM: kidYTop = height - childHeight - bottomInset; break; default: case NS_STYLE_VERTICAL_ALIGN_MIDDLE: kidYTop = height/2 - childHeight/2; } mFirstChild->MoveTo(kidRect.x, kidYTop); } void nsTableCellFrame::CreatePsuedoFrame(nsIPresContext* aPresContext) { // Do we have a prev-in-flow? if (nsnull == mPrevInFlow) { // No, create a body pseudo frame nsBodyFrame::NewFrame(&mFirstChild, mContent, this); mChildCount = 1; // Resolve style and set the style context nsIStyleContext* styleContext = aPresContext->ResolveStyleContextFor(mContent, this); // styleContext: ADDREF++ mFirstChild->SetStyleContext(aPresContext,styleContext); NS_RELEASE(styleContext); // styleContext: ADDREF-- } else { nsTableCellFrame* prevFrame = (nsTableCellFrame *)mPrevInFlow; nsIFrame* prevPseudoFrame = prevFrame->mFirstChild; NS_ASSERTION(prevFrame->ChildIsPseudoFrame(prevPseudoFrame), "bad previous pseudo-frame"); // Create a continuing column nsIStyleContext* kidSC; prevPseudoFrame->GetStyleContext(aPresContext, kidSC); prevPseudoFrame->CreateContinuingFrame(aPresContext, this, kidSC, mFirstChild); NS_RELEASE(kidSC); mChildCount = 1; } } /* * Should this be performanced tuned? This is called * in resize/reflow. Maybe we should cache the table * frame in the table cell frame. * */ nsTableFrame* nsTableCellFrame::GetTableFrame() { nsIFrame* frame = nsnull; nsresult result; result = GetContentParent(frame); // Get RowFrame if ((result == NS_OK) && (frame != nsnull)) frame->GetContentParent(frame); // Get RowGroupFrame if ((result == NS_OK) && (frame != nsnull)) frame->GetContentParent(frame); // Get TableFrame return (nsTableFrame*)frame; } /** */ NS_METHOD nsTableCellFrame::Reflow(nsIPresContext* aPresContext, nsReflowMetrics& aDesiredSize, const nsReflowState& aReflowState, nsReflowStatus& aStatus) { NS_PRECONDITION(nsnull!=aPresContext, "bad arg"); #ifdef NS_DEBUG //PreReflowCheck(); #endif // Initialize out parameter if (nsnull != aDesiredSize.maxElementSize) { aDesiredSize.maxElementSize->width = 0; aDesiredSize.maxElementSize->height = 0; } aStatus = NS_FRAME_COMPLETE; if (PR_TRUE==gsDebug || PR_TRUE==gsDebugNT) printf("%p nsTableCellFrame::Reflow: maxSize=%d,%d\n", this, aReflowState.maxSize.width, aReflowState.maxSize.height); mFirstContentOffset = mLastContentOffset = 0; nsSize availSize(aReflowState.maxSize); nsSize maxElementSize; nsSize *pMaxElementSize = aDesiredSize.maxElementSize; if (NS_UNCONSTRAINEDSIZE==aReflowState.maxSize.width) pMaxElementSize = &maxElementSize; nscoord x = 0; // SEC: what about ascent and decent??? // Compute the insets (sum of border and padding) const nsStyleSpacing* spacing = (const nsStyleSpacing*)mStyleContext->GetStyleData(eStyleStruct_Spacing); nsMargin borderPadding; spacing->CalcBorderPaddingFor(this, borderPadding); nscoord topInset = borderPadding.top; nscoord rightInset = borderPadding.right; nscoord bottomInset = borderPadding.bottom; nscoord leftInset = borderPadding.left; // Get the margin information, available space should // be reduced accordingly nsMargin margin(0,0,0,0); nsTableFrame* tableFrame = GetTableFrame(); tableFrame->GetCellMarginData(this,margin); mLastContentIsComplete = PR_TRUE; // get frame, creating one if needed if (nsnull==mFirstChild) { CreatePsuedoFrame(aPresContext); } // reduce available space by insets, if we're in a constrained situation if (NS_UNCONSTRAINEDSIZE!=availSize.width) availSize.width -= leftInset+rightInset; if (NS_UNCONSTRAINEDSIZE!=availSize.height) availSize.height -= topInset+bottomInset+margin.top+margin.bottom; // Try to reflow the child into the available space. It might not // fit or might need continuing. if (availSize.height < 0) availSize.height = 1; nsSize maxKidElementSize; if (gsDebug==PR_TRUE) printf(" nsTableCellFrame::ResizeReflow calling ReflowChild with availSize=%d,%d\n", availSize.width, availSize.height); nsReflowMetrics kidSize(pMaxElementSize); kidSize.width=kidSize.height=kidSize.ascent=kidSize.descent=0; SetPriorAvailWidth(aReflowState.maxSize.width); nsReflowState kidReflowState(mFirstChild, aReflowState, availSize); mFirstChild->WillReflow(*aPresContext); mFirstChild->MoveTo(leftInset, topInset); aStatus = ReflowChild(mFirstChild, aPresContext, kidSize, kidReflowState); SetPriorDesiredSize(kidSize); if (PR_TRUE==gsDebug || PR_TRUE==gsDebugNT) { if (nsnull!=pMaxElementSize) printf(" %p cellFrame child returned desiredSize=%d,%d,\ and maxElementSize=%d,%d\n", this, kidSize.width, kidSize.height, pMaxElementSize->width, pMaxElementSize->height); else printf(" %p cellFrame child returned desiredSize=%d,%d,\ and maxElementSize=nsnull\n", this, kidSize.width, kidSize.height); } // Set our last content offset based on the pseudo-frame nsBodyFrame* bodyPseudoFrame = (nsBodyFrame*)mFirstChild; mLastContentOffset = bodyPseudoFrame->GetLastContentOffset(); // Place the child mFirstChild->SetRect(nsRect(leftInset, topInset, kidSize.width, kidSize.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). mLastContentIsComplete = PR_FALSE; } // Return our size and our result // first, compute the height // the height can be set w/o being restricted by aMaxSize.height nscoord cellHeight = kidSize.height; if (NS_UNCONSTRAINEDSIZE!=aReflowState.maxSize.height) { cellHeight += topInset + bottomInset; } if (PR_TRUE==gsDebugNT) printf(" %p cellFrame height set to %d from kidSize=%d and insets %d,%d\n", this, cellHeight, kidSize.height, topInset, bottomInset); // next determine the cell's width nscoord cellWidth = kidSize.width; // at this point, we've factored in the cell's style attributes cellWidth += leftInset + rightInset; // factor in insets // Nav4 hack for 0 width cells. If the cell has any content, it must have a desired width of at least 1 /* if (0==cellWidth) { PRInt32 childCount; mFirstChild->ChildCount(childCount); if (0!=childCount) { nsIFrame *grandChild; mFirstChild->FirstChild(grandChild); grandChild->ChildCount(childCount); if (0!=childCount) { cellWidth=1; if (nsnull!=aDesiredSize.maxElementSize && 0==pMaxElementSize->width) pMaxElementSize->width=1; } } } */ // end Nav4 hack for 0 width cells // set the cell's desired size and max element size aDesiredSize.width = cellWidth; aDesiredSize.height = cellHeight; aDesiredSize.ascent = topInset; aDesiredSize.descent = bottomInset; if (nsnull!=aDesiredSize.maxElementSize) { *aDesiredSize.maxElementSize = *pMaxElementSize; aDesiredSize.maxElementSize->height += topInset + bottomInset; aDesiredSize.maxElementSize->width += leftInset + rightInset; } if (PR_TRUE==gsDebug || PR_TRUE==gsDebugNT) printf(" %p cellFrame returning aDesiredSize=%d,%d\n", this, aDesiredSize.width, aDesiredSize.height); #ifdef NS_DEBUG //PostReflowCheck(result); #endif return NS_OK; } NS_METHOD nsTableCellFrame::CreateContinuingFrame(nsIPresContext* aPresContext, nsIFrame* aParent, nsIStyleContext* aStyleContext, nsIFrame*& aContinuingFrame) { nsTableCellFrame* cf = new nsTableCellFrame(mContent, aParent); if (nsnull == cf) { return NS_ERROR_OUT_OF_MEMORY; } PrepareContinuingFrame(aPresContext, aParent, aStyleContext, cf); aContinuingFrame = cf; return NS_OK; } /** * * Update the border style to map to the HTML border style * */ void nsTableCellFrame::MapHTMLBorderStyle(nsIPresContext* aPresContext, nsStyleSpacing& aSpacingStyle, nscoord aBorderWidth) { nsStyleCoord width; width.SetCoordValue(aBorderWidth); aSpacingStyle.mBorder.SetTop(width); aSpacingStyle.mBorder.SetLeft(width); aSpacingStyle.mBorder.SetBottom(width); aSpacingStyle.mBorder.SetRight(width); aSpacingStyle.mBorderStyle[NS_SIDE_TOP] = NS_STYLE_BORDER_STYLE_SOLID; aSpacingStyle.mBorderStyle[NS_SIDE_LEFT] = NS_STYLE_BORDER_STYLE_SOLID; aSpacingStyle.mBorderStyle[NS_SIDE_BOTTOM] = NS_STYLE_BORDER_STYLE_SOLID; aSpacingStyle.mBorderStyle[NS_SIDE_RIGHT] = NS_STYLE_BORDER_STYLE_SOLID; nsTableFrame* tableFrame = GetTableFrame(); nsIStyleContext* styleContext = nsnull; tableFrame->GetStyleContext(aPresContext,styleContext); const nsStyleColor* colorData = (const nsStyleColor*)styleContext->GetStyleData(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(); 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 the border color is white, then shift to grey if (borderColor == 0xFFFFFFFF) borderColor = 0xFFC0C0C0; aSpacingStyle.mBorderColor[NS_SIDE_TOP] = aSpacingStyle.mBorderColor[NS_SIDE_LEFT] = aSpacingStyle.mBorderColor[NS_SIDE_BOTTOM] = aSpacingStyle.mBorderColor[NS_SIDE_RIGHT] = borderColor; } PRBool nsTableCellFrame::ConvertToPixelValue(nsHTMLValue& aValue, PRInt32 aDefault, PRInt32& aResult) { PRInt32 result = 0; 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 nsTableCellFrame::MapBorderMarginPadding(nsIPresContext* aPresContext) { // 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 nscoord padding = 0; nscoord spacing = 0; nscoord border = 1; float p2t = aPresContext->GetPixelsToTwips(); nsTableFrame* tableFrame = GetTableFrame(); tableFrame->GetGeometricParent((nsIFrame *&)tableFrame); // get the outer frame NS_ASSERTION(tableFrame,"Table Must not be null"); if (!tableFrame) return; // get the table frame style context, and from it get cellpadding, cellspacing, and border info nsIStyleContextPtr tableSC; tableFrame->GetStyleContext(aPresContext, tableSC.AssignRef()); nsStyleTable* tableStyle = (nsStyleTable*)tableSC->GetStyleData(eStyleStruct_Table); nsStyleSpacing* tableSpacingStyle = (nsStyleSpacing*)tableSC->GetStyleData(eStyleStruct_Spacing); nsStyleSpacing* spacingData = (nsStyleSpacing*)mStyleContext->GetMutableStyleData(eStyleStruct_Spacing); // check to see if cellpadding or cellspacing is defined if (tableStyle->mCellPadding.GetUnit() != eStyleUnit_Null || tableStyle->mCellSpacing.GetUnit() != eStyleUnit_Null) { spacingData->mMargin.SetTop(tableStyle->mCellSpacing); spacingData->mMargin.SetLeft(tableStyle->mCellSpacing); spacingData->mMargin.SetBottom(tableStyle->mCellSpacing); spacingData->mMargin.SetRight(tableStyle->mCellSpacing); spacingData->mPadding.SetTop(tableStyle->mCellPadding); spacingData->mPadding.SetLeft(tableStyle->mCellPadding); spacingData->mPadding.SetBottom(tableStyle->mCellPadding); spacingData->mPadding.SetRight(tableStyle->mCellPadding); } // get border information from the table if (tableSpacingStyle->mBorder.GetTopUnit() == eStyleUnit_Coord) { nsStyleCoord borderWidth; tableSpacingStyle->mBorder.GetTop(borderWidth); if (0!=borderWidth.GetCoordValue()) { // in HTML, cell borders are always 1 pixel by default border = (nscoord)(1*(aPresContext->GetPixelsToTwips())); MapHTMLBorderStyle(aPresContext, *spacingData, border); } } } void nsTableCellFrame::MapTextAttributes(nsIPresContext* aPresContext) { nsHTMLValue value; ((nsHTMLTagContent*)mContent)->GetAttribute(nsHTMLAtoms::align, value); if (value.GetUnit() == eHTMLUnit_Enumerated) { nsStyleText* text = (nsStyleText*)mStyleContext->GetMutableStyleData(eStyleStruct_Text); text->mTextAlign = value.GetIntValue(); } } // Subclass hook for style post processing NS_METHOD nsTableCellFrame::DidSetStyleContext(nsIPresContext* aPresContext) { #ifdef NOISY_STYLE printf("nsTableCellFrame::DidSetStyleContext \n"); #endif MapTextAttributes(aPresContext); MapBorderMarginPadding(aPresContext); mStyleContext->RecalcAutomaticData(aPresContext); return NS_OK; } NS_METHOD nsTableCellFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr) { NS_PRECONDITION(0 != aInstancePtr, "null ptr"); if (NULL == aInstancePtr) { return NS_ERROR_NULL_POINTER; } if (aIID.Equals(kTableCellFrameCID)) { *aInstancePtr = (void*) (this); return NS_OK; } return nsContainerFrame::QueryInterface(aIID, aInstancePtr); } /* ----- static methods ----- */ nsresult nsTableCellFrame::NewFrame(nsIFrame** aInstancePtrResult, nsIContent* aContent, nsIFrame* aParent) { NS_PRECONDITION(nsnull != aInstancePtrResult, "null ptr"); if (nsnull == aInstancePtrResult) { return NS_ERROR_NULL_POINTER; } nsIFrame* it = new nsTableCellFrame(aContent, aParent); if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } *aInstancePtrResult = it; return NS_OK; } // For Debugging ONLY NS_METHOD nsTableCellFrame::MoveTo(nscoord aX, nscoord aY) { if ((aX != mRect.x) || (aY != mRect.y)) { mRect.x = aX; mRect.y = aY; nsIView* view; GetView(view); // Let the view know if (nsnull != view) { // Position view relative to it's parent, not relative to our // parent frame (our parent frame may not have a view). nsIView* parentWithView; nsPoint origin; GetOffsetFromView(origin, parentWithView); view->SetPosition(origin.x, origin.y); NS_IF_RELEASE(parentWithView); } } return NS_OK; } NS_METHOD nsTableCellFrame::SizeTo(nscoord aWidth, nscoord aHeight) { mRect.width = aWidth; mRect.height = aHeight; nsIView* view; GetView(view); // Let the view know if (nsnull != view) { view->SetDimensions(aWidth, aHeight); } return NS_OK; }