Mozilla/mozilla/layout/html/base/src/nsHTMLBullet.cpp
kipp 630af1711c Updated to new GetDesiredSize api's (so that percentage width's could be properly calculated)
git-svn-id: svn://10.0.0.236/trunk@2758 18797224-902f-48f8-a5cc-f745e15eee43
1998-05-30 21:21:11 +00:00

568 lines
17 KiB
C++

/* -*- 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 "nsHTMLTagContent.h"
#include "nsLeafFrame.h"
#include "nsBlockFrame.h"
#include "nsIPresContext.h"
#include "nsIPresShell.h"
#include "nsIRenderingContext.h"
#include "nsIFontMetrics.h"
#include "nsIStyleContext.h"
#include "nsStyleConsts.h"
#include "nsHTMLAtoms.h"
#include "nsHTMLIIDs.h"
#include "nsHTMLImage.h"
#include "prprf.h"
class Bullet : public nsHTMLTagContent {
public:
Bullet();
NS_IMETHOD IsSynthetic(PRBool& aResult);
void MapAttributesInto(nsIStyleContext* aContext,
nsIPresContext* aPresContext);
void List(FILE* out, PRInt32 aIndent) const;
virtual nsresult CreateFrame(nsIPresContext* aPresContext,
nsIFrame* aParentFrame,
nsIStyleContext* aStyleContext,
nsIFrame*& aResult);
};
class BulletFrame : public nsLeafFrame {
public:
BulletFrame(nsIContent* aContent, nsIFrame* aParentFrame);
virtual ~BulletFrame();
NS_IMETHOD DeleteFrame();
NS_IMETHOD Paint(nsIPresContext &aCX,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect);
NS_IMETHOD Reflow(nsIPresContext* aCX,
nsReflowMetrics& aDesiredSize,
const nsReflowState& aReflowState,
nsReflowStatus& aStatus);
NS_IMETHOD List(FILE* out, PRInt32 aIndent) const;
protected:
virtual void GetDesiredSize(nsIPresContext* aPresContext,
const nsReflowState& aReflowState,
const nsSize& aMaxSize,
nsReflowMetrics& aDesiredSize);
/**
* Return the reflow state for the list container that contains this
* list item frame. There may be no list container (a dangling LI)
* therefore this may return nsnull.
*/
nsBlockReflowState* GetListContainerReflowState(nsIPresContext* aCX);
PRInt32 GetListItemOrdinal(nsIPresContext* aCX, nsStyleList& aMol);
void GetListItemText(nsIPresContext* aCX, nsString& aResult,
nsStyleList& aMol);
PRPackedBool mOrdinalValid;
PRInt32 mOrdinal;
nsMargin mPadding;
nsHTMLImageLoader mImageLoader;
};
//----------------------------------------------------------------------
Bullet::Bullet()
{
}
void
Bullet::MapAttributesInto(nsIStyleContext* aContext,
nsIPresContext* aPresContext)
{
// Force display to be inline (bullet's are not block level)
nsStyleDisplay* display = (nsStyleDisplay*)
aContext->GetData(eStyleStruct_Display);
display->mDisplay = NS_STYLE_DISPLAY_INLINE;
}
NS_METHOD
Bullet::IsSynthetic(PRBool& aResult)
{
aResult = PR_TRUE;
return NS_OK;
}
void
Bullet::List(FILE* out, PRInt32 aIndent) const
{
for (PRInt32 i = aIndent; --i >= 0; ) fputs(" ", out);
fprintf(out, "Bullet RefCnt=%d<>\n", mRefCnt);
}
nsresult
Bullet::CreateFrame(nsIPresContext* aPresContext,
nsIFrame* aParentFrame,
nsIStyleContext* aStyleContext,
nsIFrame*& aResult)
{
BulletFrame* frame = new BulletFrame(this, aParentFrame);
if (nsnull == frame) {
return NS_ERROR_OUT_OF_MEMORY;
}
aResult = frame;
frame->SetStyleContext(aPresContext, aStyleContext);
return NS_OK;
}
nsresult
NS_NewHTMLBullet(nsIHTMLContent** aInstancePtrResult)
{
Bullet* it = new Bullet();
if (nsnull == it) {
return NS_ERROR_OUT_OF_MEMORY;
}
return it->QueryInterface(kIHTMLContentIID, (void**) aInstancePtrResult);
}
//----------------------------------------------------------------------
BulletFrame::BulletFrame(nsIContent* aContent, nsIFrame* aParentFrame)
: nsLeafFrame(aContent, aParentFrame)
{
}
BulletFrame::~BulletFrame()
{
}
NS_METHOD
BulletFrame::DeleteFrame()
{
// Release image loader first so that it's refcnt can go to zero
mImageLoader.DestroyLoader();
return nsLeafFrame::DeleteFrame();
}
NS_METHOD
BulletFrame::List(FILE* out, PRInt32 aIndent) const
{
PRInt32 i;
for (i = aIndent; --i >= 0; ) fputs(" ", out);
PRInt32 contentIndex;
GetContentIndex(contentIndex);
fprintf(out, "Bullet(%d)@%p ",
contentIndex, this);
out << mRect;
if (0 != mState) {
fprintf(out, " [state=%08x]", mState);
}
fputs("<>\n", out);
return NS_OK;
}
NS_METHOD
BulletFrame::Paint(nsIPresContext& aCX,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect)
{
nsStyleDisplay* disp =
(nsStyleDisplay*)mStyleContext->GetData(eStyleStruct_Display);
if (disp->mVisible) {
nsStyleFont* myFont =
(nsStyleFont*)mStyleContext->GetData(eStyleStruct_Font);
nsStyleColor* myColor =
(nsStyleColor*)mStyleContext->GetData(eStyleStruct_Color);
nsStyleList* myList =
(nsStyleList*)mStyleContext->GetData(eStyleStruct_List);
if (myList->mListStyleImage.Length() > 0) {
nsIImage* image = mImageLoader.GetImage();
if (nsnull == image) {
// No image yet
return NS_OK;
}
nsRect innerArea(mPadding.left, mPadding.top,
mRect.width - (mPadding.left + mPadding.right),
mRect.height - (mPadding.top + mPadding.bottom));
aRenderingContext.DrawImage(image, innerArea);
}
else {
nsIFontMetrics* fm;
aRenderingContext.SetColor(myColor->mColor);
nsAutoString text;
switch (myList->mListStyleType) {
case NS_STYLE_LIST_STYLE_NONE:
break;
default:
case NS_STYLE_LIST_STYLE_BASIC:
case NS_STYLE_LIST_STYLE_DISC:
aRenderingContext.FillEllipse(mPadding.left, mPadding.top,
mRect.width - (mPadding.left + mPadding.right),
mRect.height - (mPadding.top + mPadding.bottom));
break;
case NS_STYLE_LIST_STYLE_CIRCLE:
aRenderingContext.DrawEllipse(mPadding.left, mPadding.top,
mRect.width - (mPadding.left + mPadding.right),
mRect.height - (mPadding.top + mPadding.bottom));
break;
case NS_STYLE_LIST_STYLE_SQUARE:
aRenderingContext.FillRect(mPadding.left, mPadding.top,
mRect.width - (mPadding.left + mPadding.right),
mRect.height - (mPadding.top + mPadding.bottom));
break;
case NS_STYLE_LIST_STYLE_DECIMAL:
case NS_STYLE_LIST_STYLE_LOWER_ROMAN:
case NS_STYLE_LIST_STYLE_UPPER_ROMAN:
case NS_STYLE_LIST_STYLE_LOWER_ALPHA:
case NS_STYLE_LIST_STYLE_UPPER_ALPHA:
fm = aCX.GetMetricsFor(myFont->mFont);
GetListItemText(&aCX, text, *myList);
aRenderingContext.SetFont(myFont->mFont);
aRenderingContext.DrawString(text, mPadding.left, mPadding.top,
fm->GetWidth(text));
NS_RELEASE(fm);
break;
}
}
}
return NS_OK;
}
NS_METHOD
BulletFrame::Reflow(nsIPresContext* aCX,
nsReflowMetrics& aDesiredSize,
const nsReflowState& aReflowState,
nsReflowStatus& aStatus)
{
GetDesiredSize(aCX, aReflowState, aReflowState.maxSize, aDesiredSize);
if (nsnull != aDesiredSize.maxElementSize) {
aDesiredSize.maxElementSize->width = aDesiredSize.width;
aDesiredSize.maxElementSize->height = aDesiredSize.height;
}
aStatus = NS_FRAME_COMPLETE;
return NS_OK;
}
// Return the reflow state for the list container that contains this
// list item frame. There may be no list container (a dangling LI)
// therefore this may return nsnull.
nsBlockReflowState*
BulletFrame::GetListContainerReflowState(nsIPresContext* aCX)
{
nsBlockReflowState* state = nsnull;
nsIFrame* parent = mGeometricParent;
while (nsnull != parent) {
void* ft;
nsresult status = parent->QueryInterface(kBlockFrameCID, &ft);
if (NS_OK == status) {
// The parent is a block. See if its content object is a list
// container. Only UL, OL, MENU or DIR can be list containers.
// XXX need something more flexible, say style?
nsIContent* parentContent;
parent->GetContent(parentContent);
nsIAtom* tag = parentContent->GetTag();
NS_RELEASE(parentContent);
if ((tag == nsHTMLAtoms::ul) || (tag == nsHTMLAtoms::ol) ||
(tag == nsHTMLAtoms::menu) || (tag == nsHTMLAtoms::dir)) {
NS_RELEASE(tag);
break;
}
NS_RELEASE(tag);
}
parent->GetGeometricParent(parent);
}
if (nsnull != parent) {
nsIPresShell* shell = aCX->GetShell();
state = (nsBlockReflowState*) shell->GetCachedData(parent);
NS_RELEASE(shell);
}
return state;
}
PRInt32
BulletFrame::GetListItemOrdinal(nsIPresContext* aCX,
nsStyleList& aListStyle)
{
if (mOrdinalValid) {
return mOrdinal;
}
PRInt32 ordinal = 0;
// Get block reflow state for the list container
nsBlockReflowState* state = GetListContainerReflowState(aCX);
// Try to get value directly from the list-item, if it specifies a
// value attribute. We do this with our parent's content.
nsHTMLValue value;
nsIContent* parentContent;
mContentParent->GetContent(parentContent);
nsIHTMLContent* html = (nsIHTMLContent*) parentContent;
if (eContentAttr_HasValue == html->GetAttribute(nsHTMLAtoms::value, value)) {
if (eHTMLUnit_Integer == value.GetUnit()) {
ordinal = value.GetIntValue();
if (nsnull != state) {
state->mNextListOrdinal = ordinal + 1;
}
NS_RELEASE(html);
goto done;
}
}
NS_RELEASE(html);
// Get ordinal from block reflow state
if (nsnull != state) {
ordinal = state->mNextListOrdinal;
if (ordinal < 0) {
// This is the first list item and the list container doesn't
// have a "start" attribute. Get the starting ordinal value
// correctly set.
switch (aListStyle.mListStyleType) {
case NS_STYLE_LIST_STYLE_DECIMAL:
case NS_STYLE_LIST_STYLE_LOWER_ROMAN:
case NS_STYLE_LIST_STYLE_UPPER_ROMAN:
ordinal = 1;
break;
default:
ordinal = 0;
break;
}
}
state->mNextListOrdinal = ordinal + 1;
}
done:
mOrdinal = ordinal;
mOrdinalValid = PR_TRUE;
return ordinal;
}
static const char* gLowerRomanCharsA = "ixcm";
static const char* gUpperRomanCharsA = "IXCM";
static const char* gLowerRomanCharsB = "vld?";
static const char* gUpperRomanCharsB = "VLD?";
static const char* gLowerAlphaChars = "abcdefghijklmnopqrstuvwxyz";
static const char* gUpperAlphaChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
void
BulletFrame::GetListItemText(nsIPresContext* aCX,
nsString& result,
nsStyleList& aListStyle)
{
PRInt32 ordinal = GetListItemOrdinal(aCX, aListStyle);
char cbuf[40];
switch (aListStyle.mListStyleType) {
case NS_STYLE_LIST_STYLE_DECIMAL:
PR_snprintf(cbuf, sizeof(cbuf), "%ld", ordinal);
result.Append(cbuf);
break;
case NS_STYLE_LIST_STYLE_LOWER_ROMAN:
case NS_STYLE_LIST_STYLE_UPPER_ROMAN:
{
// XXX deal with an ordinal of ZERO
nsAutoString addOn;
nsAutoString decStr;
decStr.Append(ordinal, 10);
const PRUnichar* dp = decStr.GetUnicode();
const PRUnichar* end = dp + decStr.Length();
PRIntn len=decStr.Length();
PRIntn romanPos=len;
PRIntn n;
PRBool negative=PRBool(ordinal<0);
const char* achars;
const char* bchars;
if (aListStyle.mListStyleType == NS_STYLE_LIST_STYLE_LOWER_ROMAN) {
achars = gLowerRomanCharsA;
bchars = gLowerRomanCharsB;
} else {
achars = gUpperRomanCharsA;
bchars = gUpperRomanCharsB;
}
ordinal=(ordinal < 0) ? -ordinal : ordinal;
if (ordinal < 0) {
// XXX max negative int
break;
}
for (; dp < end; dp++)
{
romanPos--;
addOn.SetLength(0);
switch(*dp)
{
case '3': addOn.Append(achars[romanPos]);
case '2': addOn.Append(achars[romanPos]);
case '1': addOn.Append(achars[romanPos]);
break;
case '4':
addOn.Append(achars[romanPos]);
case '5': case '6':
case '7': case '8':
addOn.Append(bchars[romanPos]);
for(n=0;n<(*dp-'5');n++) {
addOn.Append(achars[romanPos]);
}
break;
case '9':
addOn.Append(achars[romanPos]);
addOn.Append(achars[romanPos+1]);
break;
default:
break;
}
result.Append(addOn);
}
}
break;
case NS_STYLE_LIST_STYLE_LOWER_ALPHA:
case NS_STYLE_LIST_STYLE_UPPER_ALPHA:
{
PRInt32 anOffset = -1;
PRInt32 aBase = 26;
PRInt32 ndex=0;
PRInt32 root=1;
PRInt32 next=aBase;
PRInt32 expn=1;
const char* chars =
(aListStyle.mListStyleType == NS_STYLE_LIST_STYLE_LOWER_ALPHA)
? gLowerAlphaChars : gUpperAlphaChars;
// must be positive here...
ordinal = (ordinal < 0) ? -ordinal : ordinal;
if (ordinal < 0) {
// XXX max negative int
break;
}
while (next<=ordinal) // scale up in baseN; exceed current value.
{
root=next;
next*=aBase;
expn++;
}
while(0!=(expn--))
{
ndex = ((root<=ordinal) && (0!=root)) ? (ordinal/root): 0;
ordinal %= root;
if (root>1)
result.Append(chars[ndex+anOffset]);
else
result.Append(chars[ndex]);
root /= aBase;
}
}
break;
}
result.Append(".");
}
// from laytext.c
#define MIN_BULLET_SIZE 5
void
BulletFrame::GetDesiredSize(nsIPresContext* aCX,
const nsReflowState& aReflowState,
const nsSize& aMaxSize,
nsReflowMetrics& aDesiredSize)
{
nsStyleList* myList =
(nsStyleList*)mStyleContext->GetData(eStyleStruct_List);
nsStyleFont* myFont =
(nsStyleFont*)mStyleContext->GetData(eStyleStruct_Font);
nsIFontMetrics* fm = aCX->GetMetricsFor(myFont->mFont);
if (myList->mListStyleImage.Length() > 0) {
mImageLoader.SetURL(myList->mListStyleImage);
mImageLoader.GetDesiredSize(aCX, aReflowState, aMaxSize, aDesiredSize);
aDesiredSize.ascent = aDesiredSize.height;
aDesiredSize.descent = 0;
}
else {
nscoord bulletSize;
float p2t;
float t2p;
nsAutoString text;
switch (myList->mListStyleType) {
case NS_STYLE_LIST_STYLE_NONE:
aDesiredSize.width = 0;
aDesiredSize.height = 0;
aDesiredSize.ascent = 0;
aDesiredSize.descent = 0;
break;
default:
case NS_STYLE_LIST_STYLE_DISC:
case NS_STYLE_LIST_STYLE_CIRCLE:
case NS_STYLE_LIST_STYLE_BASIC:
case NS_STYLE_LIST_STYLE_SQUARE:
t2p = aCX->GetTwipsToPixels();
bulletSize = nscoord(t2p * fm->GetMaxAscent() / 2);
if (bulletSize < 1) {
bulletSize = MIN_BULLET_SIZE;
}
p2t = aCX->GetPixelsToTwips();
bulletSize = nscoord(p2t * bulletSize);
mPadding.bottom = (fm->GetMaxAscent() / 8);
if (NS_STYLE_LIST_STYLE_POSITION_INSIDE == myList->mListStylePosition) {
mPadding.right = bulletSize / 2;
}
aDesiredSize.width = mPadding.right + bulletSize;
aDesiredSize.height = mPadding.bottom + bulletSize;
aDesiredSize.ascent = mPadding.bottom + bulletSize;
aDesiredSize.descent = 0;
break;
case NS_STYLE_LIST_STYLE_DECIMAL:
case NS_STYLE_LIST_STYLE_LOWER_ROMAN:
case NS_STYLE_LIST_STYLE_UPPER_ROMAN:
case NS_STYLE_LIST_STYLE_LOWER_ALPHA:
case NS_STYLE_LIST_STYLE_UPPER_ALPHA:
GetListItemText(aCX, text, *myList);
if (NS_STYLE_LIST_STYLE_POSITION_INSIDE == myList->mListStylePosition) {
// Inside bullets need some extra width to get the padding
// between the list item and the content that follows.
mPadding.right = fm->GetHeight() / 2; // From old layout engine
}
aDesiredSize.width = mPadding.right + fm->GetWidth(text);
aDesiredSize.height = fm->GetHeight();
aDesiredSize.ascent = fm->GetMaxAscent();
aDesiredSize.descent = fm->GetMaxDescent();
break;
}
}
NS_RELEASE(fm);
}