/** * 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); TQPainter* p = paintInfo.p; #ifdef DEBUG_LAYOUT kdDebug( 6040 ) << nodeName().string() << "(ListMarker)::paintObject(" << _tx << ", " << _ty << ")" << endl; #endif p->setFont(style()->font()); const TQFontMetrics 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( TQPoint( _tx + xoff, _ty ), m_listImage->pixmap()); return; } #ifdef BOX_DEBUG p->setPen( Qt::red ); p->drawRect( _tx + xoff, _ty + yoff, offset, offset ); #endif const TQColor 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 TQPointArray diamond(4); int x = _tx + xoff; int y = _ty + (3 * yoff)/2 - 1; int s = (offset>>2)+1; diamond[0] = TQPoint(x+s, y); diamond[1] = TQPoint(x+2*s, y+s); diamond[2] = TQPoint(x+s, y+2*s); diamond[3] = TQPoint(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|TQt::DontClip, m_item); p->drawText(_tx + fm.width(m_item), _ty, 0, 0, Qt::AlignLeft|TQt::DontClip, TQString::fromLatin1(". ")); } else { const TQString& punct(TQString::fromLatin1(" .")); p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft|TQt::DontClip, punct); p->drawText(_tx + fm.width(punct), _ty, 0, 0, Qt::AlignLeft|TQt::DontClip, m_item); } } else { if (style()->direction() == LTR) { const TQString& punct(TQString::fromLatin1(". ")); p->drawText(_tx-offset/2, _ty, 0, 0, Qt::AlignRight|TQt::DontClip, punct); p->drawText(_tx-offset/2-fm.width(punct), _ty, 0, 0, Qt::AlignRight|TQt::DontClip, m_item); } else { const TQString& punct(TQString::fromLatin1(" .")); p->drawText(_tx+offset/2, _ty, 0, 0, Qt::AlignLeft|TQt::DontClip, punct); p->drawText(_tx+offset/2+fm.width(punct), _ty, 0, 0, Qt::AlignLeft|TQt::DontClip, m_item); } } } } } void RenderListMarker::layout() { KHTMLAssert( needsLayout() ); if ( !minMaxKnown() ) calcMinMaxWidth(); setNeedsLayout(false); } void RenderListMarker::setPixmap( const TQPixmap &p, const TQRect& 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 TQFontMetrics &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); TQString num = TQString::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(TQString::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