/* -*- 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 "nsHTMLParts.h" #include "nsIHTMLContent.h" #include "nsLeafFrame.h" #include "nsIRenderingContext.h" #include "nsGlobalVariables.h" #include "nsIStyleContext.h" #include "nsColor.h" #include "nsIPresContext.h" #include "nsIStyleContext.h" #include "nsHTMLIIDs.h" #include "nsHTMLAtoms.h" #include "nsIFontMetrics.h" #include "nsIHTMLAttributes.h" #include "nsStyleConsts.h" #include "nsCSSRendering.h" #include "nsCSSLayout.h" #include "nsIDOMHTMLHRElement.h" static NS_DEFINE_IID(kIDOMHTMLHRElementIID, NS_IDOMHTMLHRELEMENT_IID); // default hr thickness in pixels #define DEFAULT_THICKNESS 3 class HRuleFrame : public nsLeafFrame { public: HRuleFrame(nsIContent* aContent, nsIFrame* aParentFrame); NS_IMETHOD Reflow(nsIPresContext& aPresContext, nsReflowMetrics& aDesiredSize, const nsReflowState& aReflowState, nsReflowStatus& aStatus); NS_IMETHOD Paint(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect); NS_IMETHOD AttributeChanged(nsIPresShell* aShell, nsIPresContext* aPresContext, nsIContent* aChild, nsIAtom* aAttribute); protected: virtual ~HRuleFrame(); virtual void GetDesiredSize(nsIPresContext* aPresContext, const nsReflowState& aReflowState, nsReflowMetrics& aDesiredSize); PRBool GetNoShade(); PRInt32 GetThickness(); nscoord mComputedWidth; }; nsresult NS_NewHRFrame(nsIContent* aContent, nsIFrame* aParentFrame, nsIFrame*& aResult) { nsIFrame* frame = new HRuleFrame(aContent, aParentFrame); if (nsnull == frame) { return NS_ERROR_OUT_OF_MEMORY; } aResult = frame; return NS_OK; } HRuleFrame::HRuleFrame(nsIContent* aContent, nsIFrame* aParentFrame) : nsLeafFrame(aContent, aParentFrame) { } HRuleFrame::~HRuleFrame() { } NS_IMETHODIMP HRuleFrame::AttributeChanged(nsIPresShell* aShell, nsIPresContext* aPresContext, nsIContent* aChild, nsIAtom* aAttribute) { if (nsHTMLAtoms::noshade == aAttribute) { // Trigger an immediate rendering Invalidate(nsRect(0, 0, mRect.width, mRect.height), PR_TRUE); } return NS_OK; } NS_METHOD HRuleFrame::Paint(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect) { const nsStyleDisplay* disp = (const nsStyleDisplay*)mStyleContext->GetStyleData(eStyleStruct_Display); if (PR_FALSE == disp->mVisible) { return NS_OK; } float p2t = aPresContext.GetPixelsToTwips(); nscoord thickness = NSIntPixelsToTwips(GetThickness(), p2t); // Get style data const nsStyleSpacing* spacing = (const nsStyleSpacing*) mStyleContext->GetStyleData(eStyleStruct_Spacing); const nsStyleColor* color = (const nsStyleColor*) mStyleContext->GetStyleData(eStyleStruct_Color); nsMargin borderPadding; spacing->CalcBorderPaddingFor(this, borderPadding); nscoord x0 = borderPadding.left; nscoord y0 = borderPadding.top; nscoord width = mRect.width - (borderPadding.left + borderPadding.right); nscoord height = mRect.height - (borderPadding.top + borderPadding.bottom); nscoord newWidth = mComputedWidth; if (newWidth < width) { // center or right align rule within the extra space const nsStyleText* text = (const nsStyleText*) mStyleContext->GetStyleData(eStyleStruct_Text); switch (text->mTextAlign) { case NS_STYLE_TEXT_ALIGN_RIGHT: x0 += width - newWidth; break; case NS_STYLE_TEXT_ALIGN_LEFT: break; default: case NS_STYLE_TEXT_ALIGN_CENTER: x0 += (width - newWidth) / 2; break; } } width = newWidth; // Center hrule vertically within the available space y0 += (height - thickness) / 2; height = thickness; // To shade or not to shade, that is the question. Begin by collecting the // three decision criteria: rendering to the printer or the display, is the // "Beveled Lines" checkbox set in the page setup dialog, and does the tag // have the NOSHADE attribute set. PRBool printing = nsGlobalVariables::Instance()->GetPrinting(&aPresContext); PRBool bevel = nsGlobalVariables::Instance()->GetBeveledLines(); PRBool noShadeAttribute = GetNoShade(); // Now that we have the data to make the shading criteria, we next // collect the decision criteria for rending in solid black: // printing (which we already have) and the "Black Lines" setting in // the page setup dialog PRBool blackLines = nsGlobalVariables::Instance()->GetBlackLines(); nscolor colors[2]; // Get the background color that applies to this HR if (printing && blackLines) { colors[0] = NS_RGB(0,0,0); colors[1] = colors[0]; } else { // XXX Get correct color by finding the first parent that actually // specifies a color. NS_Get3DColors(colors, color->mBackgroundColor); } // Draw a "shadowed" box around the rule area if (!noShadeAttribute && ((printing && bevel) || !printing)) { // Lines render inclusively on the both the starting and ending // coordinate, so reduce the end coordinates by one pixel. nscoord x1 = nscoord(x0 + width - NSIntPixelsToTwips(1, p2t)); nscoord y1 = nscoord(y0 + height - NSIntPixelsToTwips(1, p2t)); // Draw bottom and right lines aRenderingContext.SetColor (colors[1]); aRenderingContext.DrawLine (x1, y0, x1, y1); aRenderingContext.DrawLine (x1, y1, x0, y1); // Draw top and left lines aRenderingContext.SetColor (colors[0]); aRenderingContext.DrawLine (x0, y1, x0, y0); aRenderingContext.DrawLine (x0, y0, x1, y0); } else { // When a rule is not shaded, then we use a uniform color and // draw half-circles on the end points. aRenderingContext.SetColor (colors[0]); nscoord diameter = height; if ((diameter > width) || (diameter < NSIntPixelsToTwips(3, p2t))) { // The half-circles on the ends of the rule aren't going to // look right so don't bother drawing them. aRenderingContext.FillRect(x0, y0, width, height); } else { aRenderingContext.FillArc(x0, y0, diameter, diameter, 90.0f, 270.0f); aRenderingContext.FillArc(x0 + width - diameter, y0, diameter, diameter, 270.0f, 180.0f); aRenderingContext.FillRect(x0 + diameter/2, y0, width - diameter, height); } } return NS_OK; } NS_IMETHODIMP HRuleFrame::Reflow(nsIPresContext& aPresContext, nsReflowMetrics& aDesiredSize, const nsReflowState& aReflowState, nsReflowStatus& aStatus) { NS_PRECONDITION(mState & NS_FRAME_IN_REFLOW, "frame is not in reflow"); // XXX add in code to check for width/height being set via css // and if set use them instead of calling GetDesiredSize. GetDesiredSize(&aPresContext, aReflowState, aDesiredSize); AddBordersAndPadding(&aPresContext, aDesiredSize); // HR's do not impact the max-element-size, unless a width is specified // otherwise tables behave badly. This makes sense they are springy. if (nsnull != aDesiredSize.maxElementSize) { nscoord onePixel = NSIntPixelsToTwips(1, aPresContext.GetPixelsToTwips()); nsSize size; PRIntn ss = nsCSSLayout::GetStyleSize(&aPresContext, aReflowState, size); if (NS_SIZE_HAS_WIDTH & ss) { aDesiredSize.maxElementSize->width = size.width; aDesiredSize.maxElementSize->height = onePixel; } else { aDesiredSize.maxElementSize->width = onePixel; aDesiredSize.maxElementSize->height = onePixel; } } aStatus = NS_FRAME_COMPLETE; return NS_OK; } void HRuleFrame::GetDesiredSize(nsIPresContext* aPresContext, const nsReflowState& aReflowState, nsReflowMetrics& aDesiredSize) { nsSize size; PRIntn ss = nsCSSLayout::GetStyleSize(aPresContext, aReflowState, size); if (NS_SIZE_HAS_WIDTH & ss) { aDesiredSize.width = size.width; } else { if (NS_UNCONSTRAINEDSIZE == aReflowState.maxSize.width) { aDesiredSize.width = nscoord(aPresContext->GetPixelsToTwips()); } else { aDesiredSize.width = aReflowState.maxSize.width; } } mComputedWidth = aDesiredSize.width; if (aReflowState.maxSize.width != NS_UNCONSTRAINEDSIZE) { if (aDesiredSize.width < aReflowState.maxSize.width) { aDesiredSize.width = aReflowState.maxSize.width; } } // XXX should we interpret css's height property as thickness? or as // line-height? In the meantime, ignore it... nscoord lineHeight; // Get the thickness of the rule (this is not css's height property) float p2t = aPresContext->GetPixelsToTwips(); nscoord thickness = NSIntPixelsToTwips(GetThickness(), p2t); // Compute height of "line" that hrule will layout within. Use the // default font to do this. lineHeight = thickness + NSIntPixelsToTwips(2, p2t); const nsFont& defaultFont = aPresContext->GetDefaultFont(); nsIFontMetrics* fm = aPresContext->GetMetricsFor(defaultFont); nscoord defaultLineHeight; fm->GetHeight(defaultLineHeight); NS_RELEASE(fm); if (lineHeight < defaultLineHeight) { lineHeight = defaultLineHeight; } aDesiredSize.height = lineHeight; aDesiredSize.ascent = lineHeight; aDesiredSize.descent = 0; } PRInt32 HRuleFrame::GetThickness() { PRInt32 result = DEFAULT_THICKNESS; // See if the underlying content is an HR that also implements the // nsIHTMLContent API. // XXX the dependency on nsIHTMLContent is done to avoid reparsing // the size value. nsIDOMHTMLHRElement* hr = nsnull; nsIHTMLContent* hc = nsnull; mContent->QueryInterface(kIDOMHTMLHRElementIID, (void**) &hr); mContent->QueryInterface(kIHTMLContentIID, (void**) &hc); if ((nsnull != hr) && (nsnull != hc)) { // Winner. Get the size attribute to determine how thick the HR // should be. nsHTMLValue value; if (NS_CONTENT_ATTR_HAS_VALUE == hc->GetAttribute(nsHTMLAtoms::size, value)) { if (value.GetUnit() == eHTMLUnit_Pixel) { PRInt32 pixels = value.GetPixelValue(); switch (pixels) { case 0: case 1: result = 1; break; default: result = pixels + 1; break; } } } } NS_IF_RELEASE(hc); NS_IF_RELEASE(hr); return result; } PRBool HRuleFrame::GetNoShade() { PRBool result = PR_FALSE; nsIDOMHTMLHRElement* hr = nsnull; mContent->QueryInterface(kIDOMHTMLHRElementIID, (void**) &hr); if (nsnull != hr) { hr->GetNoShade(&result); NS_RELEASE(hr); } return result; }