/**
* This file is part of the HTML rendering engine for KDE.
*
* Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000-2002 Dirk Mueller (mueller@kde.org)
* (C) 2003 Apple Computer, Inc.
* (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "rendering/render_list.h"
#include "rendering/render_canvas.h"
#include "rendering/enumerate.h"
#include "rendering/counter_tree.h"
#include "html/html_listimpl.h"
#include "misc/helper.h"
#include "misc/htmltags.h"
#include "misc/loader.h"
#include "xml/dom_docimpl.h"
#include
#include
#include
//#define BOX_DEBUG
using namespace khtml;
using namespace Enumerate;
const int cMarkerPadding = 7;
// -------------------------------------------------------------------------
RenderListItem::RenderListItem(DOM::NodeImpl* node)
: RenderBlock(node)
{
// init RenderObject attributes
setInline(false); // our object is not Inline
predefVal = -1;
m_marker = 0;
m_counter = 0;
m_insideList = false;
m_deleteMarker = false;
}
void RenderListItem::setStyle(RenderStyle *_style)
{
RenderBlock::setStyle(_style);
RenderStyle *newStyle = new RenderStyle();
newStyle->ref();
newStyle->inheritFrom(style());
if(!m_marker && style()->listStyleType() != LNONE) {
m_marker = new (renderArena()) RenderListMarker(element());
m_marker->setIsAnonymous( true );
m_marker->setStyle(newStyle);
m_marker->setListItem( this );
m_deleteMarker = true;
} else if ( m_marker && style()->listStyleType() == LNONE) {
m_marker->detach();
m_marker = 0;
}
else if ( m_marker ) {
m_marker->setStyle(newStyle);
}
newStyle->deref();
}
void RenderListItem::detach()
{
if ( m_marker && m_deleteMarker )
m_marker->detach();
RenderBlock::detach();
}
static RenderObject* getParentOfFirstLineBox(RenderObject* curr, RenderObject* marker)
{
RenderObject* firstChild = curr->firstChild();
if (!firstChild)
return 0;
for (RenderObject* currChild = firstChild;
currChild; currChild = currChild->nextSibling()) {
if (currChild == marker)
continue;
if (currChild->isInline())
return curr;
if (currChild->isFloating() || currChild->isPositioned())
continue;
if (currChild->isTable() || !currChild->isRenderBlock())
break;
if (currChild->style()->htmlHacks() && currChild->element() &&
(currChild->element()->id() == ID_UL || currChild->element()->id() == ID_OL))
break;
RenderObject* lineBox = getParentOfFirstLineBox(currChild, marker);
if (lineBox)
return lineBox;
}
return 0;
}
void RenderListItem::updateMarkerLocation()
{
// Sanity check the location of our marker.
if (m_marker) {
RenderObject* markerPar = m_marker->parent();
RenderObject* lineBoxParent = getParentOfFirstLineBox(this, m_marker);
if (!lineBoxParent) {
// If the marker is currently contained inside an anonymous box,
// then we are the only item in that anonymous box (since no line box
// parent was found). It's ok to just leave the marker where it is
// in this case.
if (markerPar && markerPar->isAnonymousBlock())
lineBoxParent = markerPar;
else
lineBoxParent = this;
}
if (markerPar != lineBoxParent)
{
if (markerPar)
markerPar->removeChild(m_marker);
if (!lineBoxParent)
lineBoxParent = this;
lineBoxParent->addChild(m_marker, lineBoxParent->firstChild());
m_deleteMarker = false;
if (!m_marker->minMaxKnown())
m_marker->calcMinMaxWidth();
recalcMinMaxWidths();
}
}
}
void RenderListItem::calcMinMaxWidth()
{
// Make sure our marker is in the correct location.
updateMarkerLocation();
if (!minMaxKnown())
RenderBlock::calcMinMaxWidth();
}
/*
short RenderListItem::marginLeft() const
{
if (m_insideList)
return RenderBlock::marginLeft();
else
return kMax(m_marker->markerWidth(), RenderBlock::marginLeft());
}
short RenderListItem::marginRight() const
{
return RenderBlock::marginRight();
}*/
void RenderListItem::layout( )
{
KHTMLAssert( needsLayout() );
KHTMLAssert( minMaxKnown() );
updateMarkerLocation();
RenderBlock::layout();
}
// -----------------------------------------------------------
RenderListMarker::RenderListMarker(DOM::NodeImpl* node)
: RenderBox(node), m_listImage(0), m_markerWidth(0)
{
// init RenderObject attributes
setInline(true); // our object is Inline
setReplaced(true); // pretend to be replaced
// val = -1;
// m_listImage = 0;
}
RenderListMarker::~RenderListMarker()
{
if(m_listImage)
m_listImage->deref(this);
if (m_listItem)
m_listItem->resetListMarker();
}
void RenderListMarker::setStyle(RenderStyle *s)
{
if ( s && style() && s->listStylePosition() != style()->listStylePosition() )
setNeedsLayoutAndMinMaxRecalc();
RenderBox::setStyle(s);
if ( m_listImage != style()->listStyleImage() ) {
if(m_listImage) m_listImage->deref(this);
m_listImage = style()->listStyleImage();
if(m_listImage) m_listImage->ref(this);
}
}
void RenderListMarker::paint(PaintInfo& paintInfo, int _tx, int _ty)
{
if (paintInfo.phase != PaintActionForeground)
return;
if (style()->visibility() != VISIBLE) return;
_tx += m_x;
_ty += m_y;
if((_ty > paintInfo.r.bottom()) || (_ty + m_height <= paintInfo.r.top()))
return;
if(shouldPaintBackgroundOrBorder())
paintBoxDecorations(paintInfo, _tx, _ty);
QPainter* p = paintInfo.p;
#ifdef DEBUG_LAYOUT
kdDebug( 6040 ) << nodeName().string() << "(ListMarker)::paintObject(" << _tx << ", " << _ty << ")" << endl;
#endif
p->setFont(style()->font());
const QFontMetrics fm = p->fontMetrics();
// The marker needs to adjust its tx, for the case where it's an outside marker.
RenderObject* listItem = 0;
int leftLineOffset = 0;
int rightLineOffset = 0;
if (!listPositionInside()) {
listItem = this;
int yOffset = 0;
int xOffset = 0;
while (listItem && listItem != m_listItem) {
yOffset += listItem->yPos();
xOffset += listItem->xPos();
listItem = listItem->parent();
}
// Now that we have our xoffset within the listbox, we need to adjust ourselves by the delta
// between our current xoffset and our desired position (which is just outside the border box
// of the list item).
if (style()->direction() == LTR) {
leftLineOffset = m_listItem->leftRelOffset(yOffset, m_listItem->leftOffset(yOffset));
_tx -= (xOffset - leftLineOffset) + m_listItem->paddingLeft() + m_listItem->borderLeft();
}
else {
rightLineOffset = m_listItem->rightRelOffset(yOffset, m_listItem->rightOffset(yOffset));
_tx += (rightLineOffset-xOffset) + m_listItem->paddingRight() + m_listItem->borderRight();
}
}
int offset = fm.ascent()*2/3;
bool haveImage = m_listImage && !m_listImage->isErrorImage();
if (haveImage)
offset = m_listImage->pixmap().width();
int xoff = 0;
int yoff = fm.ascent() - offset;
int bulletWidth = offset/2;
if (offset%2)
bulletWidth++;
if (!listPositionInside()) {
if (listItem && listItem->style()->direction() == LTR)
xoff = -cMarkerPadding - offset;
else
xoff = cMarkerPadding + (haveImage ? 0 : (offset - bulletWidth));
}
else if (style()->direction() == RTL)
xoff += haveImage ? cMarkerPadding : (m_width - bulletWidth);
if ( m_listImage && !m_listImage->isErrorImage()) {
p->drawPixmap( QPoint( _tx + xoff, _ty ), m_listImage->pixmap());
return;
}
#ifdef BOX_DEBUG
p->setPen( Qt::red );
p->drawRect( _tx + xoff, _ty + yoff, offset, offset );
#endif
const QColor color( style()->color() );
p->setPen( color );
switch(style()->listStyleType()) {
case LDISC:
p->setBrush( color );
p->drawEllipse( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 );
return;
case LCIRCLE:
p->setBrush( Qt::NoBrush );
p->drawEllipse( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 );
return;
case LSQUARE:
p->setBrush( color );
p->drawRect( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 );
return;
case LBOX:
p->setBrush( Qt::NoBrush );
p->drawRect( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 );
return;
case LDIAMOND: {
static QPointArray diamond(4);
int x = _tx + xoff;
int y = _ty + (3 * yoff)/2 - 1;
int s = (offset>>2)+1;
diamond[0] = QPoint(x+s, y);
diamond[1] = QPoint(x+2*s, y+s);
diamond[2] = QPoint(x+s, y+2*s);
diamond[3] = QPoint(x, y+s);
p->setBrush( color );
p->drawConvexPolygon( diamond, 0, 4 );
return;
}
case LNONE:
return;
default:
if (!m_item.isEmpty()) {
if(listPositionInside()) {
if( style()->direction() == LTR) {
p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, m_item);
p->drawText(_tx + fm.width(m_item), _ty, 0, 0, Qt::AlignLeft|Qt::DontClip,
QString::fromLatin1(". "));
}
else {
const QString& punct(QString::fromLatin1(" ."));
p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, punct);
p->drawText(_tx + fm.width(punct), _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, m_item);
}
} else {
if (style()->direction() == LTR) {
const QString& punct(QString::fromLatin1(". "));
p->drawText(_tx-offset/2, _ty, 0, 0, Qt::AlignRight|Qt::DontClip, punct);
p->drawText(_tx-offset/2-fm.width(punct), _ty, 0, 0, Qt::AlignRight|Qt::DontClip, m_item);
}
else {
const QString& punct(QString::fromLatin1(" ."));
p->drawText(_tx+offset/2, _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, punct);
p->drawText(_tx+offset/2+fm.width(punct), _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, m_item);
}
}
}
}
}
void RenderListMarker::layout()
{
KHTMLAssert( needsLayout() );
if ( !minMaxKnown() )
calcMinMaxWidth();
setNeedsLayout(false);
}
void RenderListMarker::setPixmap( const QPixmap &p, const QRect& r, CachedImage *o)
{
if(o != m_listImage) {
RenderBox::setPixmap(p, r, o);
return;
}
if(m_width != m_listImage->pixmap_size().width() || m_height != m_listImage->pixmap_size().height())
setNeedsLayoutAndMinMaxRecalc();
else
repaintRectangle(0, 0, m_width, m_height);
}
void RenderListMarker::calcMinMaxWidth()
{
KHTMLAssert( !minMaxKnown() );
m_markerWidth = m_width = 0;
if(m_listImage && !m_listImage->isErrorImage()) {
m_markerWidth = m_listImage->pixmap().width() + cMarkerPadding;
if (listPositionInside())
m_width = m_markerWidth;
m_height = m_listImage->pixmap().height();
m_minWidth = m_maxWidth = m_width;
setMinMaxKnown();
return;
}
const QFontMetrics &fm = style()->fontMetrics();
m_height = fm.ascent();
// Skip uncounted elements
switch(style()->listStyleType()) {
// Glyphs:
case LDISC:
case LCIRCLE:
case LSQUARE:
case LBOX:
case LDIAMOND:
m_markerWidth = fm.ascent();
goto end;
default:
break;
}
{ // variable scope
CounterNode *counter = m_listItem->m_counter;
if (!counter) {
counter = m_listItem->getCounter("list-item", true);
counter->setRenderer(this);
m_listItem->m_counter = counter;
}
assert(counter);
int value = counter->count();
if (counter->isReset()) value = counter->value();
int total = value;
if (counter->parent()) total = counter->parent()->total();
switch(style()->listStyleType())
{
// Numeric:
case LDECIMAL:
m_item.setNum ( value );
break;
case DECIMAL_LEADING_ZERO: {
int decimals = 2;
int t = total/100;
while (t>0) {
t = t/10;
decimals++;
}
decimals = kMax(decimals, 2);
QString num = QString::number(value);
m_item.fill('0',decimals-num.length());
m_item.append(num);
break;
}
case ARABIC_INDIC:
m_item = toArabicIndic( value );
break;
case LAO:
m_item = toLao( value );
break;
case PERSIAN:
case URDU:
m_item = toPersianUrdu( value );
break;
case THAI:
m_item = toThai( value );
break;
case TIBETAN:
m_item = toTibetan( value );
break;
// Algoritmic:
case LOWER_ROMAN:
m_item = toRoman( value, false );
break;
case UPPER_ROMAN:
m_item = toRoman( value, true );
break;
case HEBREW:
m_item = toHebrew( value );
break;
case ARMENIAN:
m_item = toArmenian( value );
break;
case GEORGIAN:
m_item = toGeorgian( value );
break;
// Alphabetic:
case LOWER_ALPHA:
case LOWER_LATIN:
m_item = toLowerLatin( value );
break;
case UPPER_ALPHA:
case UPPER_LATIN:
m_item = toUpperLatin( value );
break;
case LOWER_GREEK:
m_item = toLowerGreek( value );
break;
case UPPER_GREEK:
m_item = toUpperGreek( value );
break;
case HIRAGANA:
m_item = toHiragana( value );
break;
case HIRAGANA_IROHA:
m_item = toHiraganaIroha( value );
break;
case KATAKANA:
m_item = toKatakana( value );
break;
case KATAKANA_IROHA:
m_item = toKatakanaIroha( value );
break;
// Ideographic:
case JAPANESE_FORMAL:
m_item = toJapaneseFormal( value );
break;
case JAPANESE_INFORMAL:
m_item = toJapaneseInformal( value );
break;
case SIMP_CHINESE_FORMAL:
m_item = toSimpChineseFormal( value );
break;
case SIMP_CHINESE_INFORMAL:
m_item = toSimpChineseInformal( value );
break;
case TRAD_CHINESE_FORMAL:
m_item = toTradChineseFormal( value );
break;
case CJK_IDEOGRAPHIC:
// CSS 3 List says treat as trad-chinese-informal
case TRAD_CHINESE_INFORMAL:
m_item = toTradChineseInformal( value );
break;
// special:
case LNONE:
break;
default:
KHTMLAssert(false);
}
m_markerWidth = fm.width(m_item) + fm.width(QString::fromLatin1(". "));
}
end:
if(listPositionInside())
m_width = m_markerWidth;
m_minWidth = m_width;
m_maxWidth = m_width;
setMinMaxKnown();
}
short RenderListMarker::lineHeight(bool /*b*/) const
{
return height();
}
short RenderListMarker::baselinePosition(bool /*b*/) const
{
return height();
}
void RenderListMarker::calcWidth()
{
RenderBox::calcWidth();
}
/*
int CounterListItem::recount() const
{
static_cast(m_renderer)->m_marker->setNeedsLayoutAndMinMaxRecalc();
}
void CounterListItem::setSelfDirty()
{
}*/
#undef BOX_DEBUG