fix MENU/DIR tags handling and default css styling.
[kdelibs.git] / khtml / rendering / render_list.cpp
blobb578758c2d5574e823d7802828d490ce053a0a92
1 /**
2 * This file is part of the HTML rendering engine for KDE.
4 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
5 * (C) 1999 Antti Koivisto (koivisto@kde.org)
6 * (C) 2000-2002 Dirk Mueller (mueller@kde.org)
7 * (C) 2003 Apple Computer, Inc.
8 * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB. If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
27 #include "rendering/render_list.h"
28 #include "rendering/render_canvas.h"
29 #include "rendering/enumerate.h"
30 #include "rendering/counter_tree.h"
31 #include "html/html_listimpl.h"
32 #include "imload/imagepainter.h"
33 #include "misc/helper.h"
34 #include "misc/htmltags.h"
35 #include "misc/loader.h"
36 #include "xml/dom_docimpl.h"
38 #include <kdebug.h>
39 #include <kglobal.h>
41 //#define BOX_DEBUG
43 using namespace khtml;
44 using namespace Enumerate;
45 using namespace khtmlImLoad;
47 const int cMarkerPadding = 7;
49 // -------------------------------------------------------------------------
51 RenderListItem::RenderListItem(DOM::NodeImpl* node)
52 : RenderBlock(node)
54 // init RenderObject attributes
55 setInline(false); // our object is not Inline
57 predefVal = -1;
58 m_marker = 0;
59 m_counter = 0;
60 m_insideList = false;
61 m_deleteMarker = false;
64 void RenderListItem::setStyle(RenderStyle *_style)
66 RenderBlock::setStyle(_style);
68 RenderStyle *newStyle = new RenderStyle();
69 newStyle->ref();
71 newStyle->inheritFrom(style());
73 if(!m_marker && style()->listStyleType() != LNONE) {
74 m_marker = new (renderArena()) RenderListMarker(element());
75 m_marker->setIsAnonymous( true );
76 m_marker->setStyle(newStyle);
77 m_marker->setListItem( this );
78 m_deleteMarker = true;
79 } else if ( m_marker && style()->listStyleType() == LNONE) {
80 m_marker->detach();
81 m_marker = 0;
83 else if ( m_marker ) {
84 m_marker->setStyle(newStyle);
87 newStyle->deref();
90 void RenderListItem::detach()
92 if ( m_marker && m_deleteMarker )
93 m_marker->detach();
94 RenderBlock::detach();
97 static RenderObject* getParentOfFirstLineBox(RenderObject* curr, RenderObject* marker)
99 RenderObject* firstChild = curr->firstChild();
100 if (!firstChild)
101 return 0;
103 for (RenderObject* currChild = firstChild;
104 currChild; currChild = currChild->nextSibling()) {
105 if (currChild == marker)
106 continue;
108 if (currChild->isInline())
109 return curr;
111 if (currChild->isFloating() || currChild->isPositioned())
112 continue;
114 if (currChild->isTable() || !currChild->isRenderBlock())
115 break;
117 if (currChild->style()->htmlHacks() && currChild->element() &&
118 (currChild->element()->id() == ID_UL || currChild->element()->id() == ID_OL ||
119 currChild->element()->id() == ID_DIR || currChild->element()->id() == ID_MENU))
120 break;
122 RenderObject* lineBox = getParentOfFirstLineBox(currChild, marker);
123 if (lineBox)
124 return lineBox;
127 return 0;
131 void RenderListItem::updateMarkerLocation()
133 // Sanity check the location of our marker.
134 if (m_marker) {
135 RenderObject* markerPar = m_marker->parent();
136 RenderObject* lineBoxParent = getParentOfFirstLineBox(this, m_marker);
137 if (!lineBoxParent) {
138 // If the marker is currently contained inside an anonymous box,
139 // then we are the only item in that anonymous box (since no line box
140 // parent was found). It's ok to just leave the marker where it is
141 // in this case.
142 if (markerPar && markerPar->isAnonymousBlock())
143 lineBoxParent = markerPar;
144 else
145 lineBoxParent = this;
147 if (markerPar != lineBoxParent)
149 if (markerPar)
150 markerPar->removeChild(m_marker);
151 if (!lineBoxParent)
152 lineBoxParent = this;
153 lineBoxParent->addChild(m_marker, lineBoxParent->firstChild());
154 m_deleteMarker = false;
155 if (!m_marker->minMaxKnown())
156 m_marker->calcMinMaxWidth();
157 recalcMinMaxWidths();
162 void RenderListItem::calcMinMaxWidth()
164 // Make sure our marker is in the correct location.
165 updateMarkerLocation();
166 if (!minMaxKnown())
167 RenderBlock::calcMinMaxWidth();
170 short RenderListItem::marginLeft() const
172 if (m_insideList)
173 return RenderBlock::marginLeft();
174 else
175 return qMax(m_marker->markerWidth(), RenderBlock::marginLeft());
178 short RenderListItem::marginRight() const
180 return RenderBlock::marginRight();
183 void RenderListItem::layout( )
185 KHTMLAssert( needsLayout() );
186 KHTMLAssert( minMaxKnown() );
188 updateMarkerLocation();
189 RenderBlock::layout();
192 // -----------------------------------------------------------
194 RenderListMarker::RenderListMarker(DOM::NodeImpl* node)
195 : RenderBox(node), m_listImage(0), m_markerWidth(0)
197 // init RenderObject attributes
198 setInline(true); // our object is Inline
199 setReplaced(true); // pretend to be replaced
200 // val = -1;
201 // m_listImage = 0;
204 RenderListMarker::~RenderListMarker()
206 if(m_listImage)
207 m_listImage->deref(this);
208 if (m_listItem)
209 m_listItem->resetListMarker();
212 void RenderListMarker::setStyle(RenderStyle *s)
214 if ( style() && s->listStylePosition() != style()->listStylePosition() )
215 setNeedsLayoutAndMinMaxRecalc();
217 RenderBox::setStyle(s);
219 if ( m_listImage != style()->listStyleImage() ) {
220 if(m_listImage) m_listImage->deref(this);
221 m_listImage = style()->listStyleImage();
222 if(m_listImage) m_listImage->ref(this);
227 void RenderListMarker::paint(PaintInfo& paintInfo, int _tx, int _ty)
229 if (paintInfo.phase != PaintActionForeground)
230 return;
232 if (style()->visibility() != VISIBLE) return;
234 _tx += m_x;
235 _ty += m_y;
237 if((_ty > paintInfo.r.bottom()) || (_ty + m_height <= paintInfo.r.top()))
238 return;
240 if(shouldPaintBackgroundOrBorder())
241 paintBoxDecorations(paintInfo, _tx, _ty);
243 QPainter* p = paintInfo.p;
244 #ifdef DEBUG_LAYOUT
245 kDebug( 6040 ) << nodeName().string() << "(ListMarker)::paintObject(" << _tx << ", " << _ty << ")" << endl;
246 #endif
247 p->setFont(style()->font());
248 const QFontMetrics fm = p->fontMetrics();
251 // The marker needs to adjust its tx, for the case where it's an outside marker.
252 RenderObject* listItem = 0;
253 int leftLineOffset = 0;
254 int rightLineOffset = 0;
255 if (!listPositionInside()) {
256 listItem = this;
257 int yOffset = 0;
258 int xOffset = 0;
259 while (listItem && listItem != m_listItem) {
260 yOffset += listItem->yPos();
261 xOffset += listItem->xPos();
262 listItem = listItem->parent();
265 // Now that we have our xoffset within the listbox, we need to adjust ourselves by the delta
266 // between our current xoffset and our desired position (which is just outside the border box
267 // of the list item).
268 if (style()->direction() == LTR) {
269 leftLineOffset = m_listItem->leftRelOffset(yOffset, m_listItem->leftOffset(yOffset));
270 _tx -= (xOffset - leftLineOffset) + m_listItem->paddingLeft() + m_listItem->borderLeft();
272 else {
273 rightLineOffset = m_listItem->rightRelOffset(yOffset, m_listItem->rightOffset(yOffset));
274 _tx += (rightLineOffset-xOffset) + m_listItem->paddingRight() + m_listItem->borderRight();
278 int offset = fm.ascent()*2/3;
279 bool haveImage = m_listImage && !m_listImage->isErrorImage();
280 if (haveImage)
281 offset = m_listImage->pixmap_size().width();
283 int xoff = 0;
284 int yoff = fm.ascent() - offset;
286 int bulletWidth = offset/2;
287 if (offset%2)
288 bulletWidth++;
289 if (!listPositionInside()) {
290 if (listItem && listItem->style()->direction() == LTR)
291 xoff = -cMarkerPadding - offset;
292 else
293 xoff = cMarkerPadding + (haveImage ? 0 : (offset - bulletWidth));
295 else if (style()->direction() == RTL)
296 xoff += haveImage ? cMarkerPadding : (m_width - bulletWidth);
298 if ( m_listImage && !m_listImage->isErrorImage()) {
299 ImagePainter painter(m_listImage->image());
300 painter.paint( _tx + xoff, _ty, p );
301 return;
304 #ifdef BOX_DEBUG
305 p->setPen( Qt::red );
306 p->drawRect( _tx + xoff, _ty + yoff, offset, offset );
307 #endif
309 const QColor color( style()->color() );
310 p->setPen( color );
312 switch(style()->listStyleType()) {
313 case LDISC:
314 p->setBrush( color );
315 p->drawEllipse( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1), (offset>>1) );
316 return;
317 case LCIRCLE:
318 p->setBrush( Qt::NoBrush );
319 p->drawEllipse( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1), (offset>>1) );
320 return;
321 case LSQUARE:
322 p->setBrush( color );
323 p->drawRect( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1), (offset>>1) );
324 return;
325 case LBOX:
326 p->setBrush( Qt::NoBrush );
327 p->drawRect( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1), (offset>>1) );
328 return;
329 case LDIAMOND: {
330 static QPolygon diamond(4);
331 int x = _tx + xoff;
332 int y = _ty + (3 * yoff)/2 - 1;
333 int s = (offset>>2)+1;
334 diamond[0] = QPoint(x+s, y);
335 diamond[1] = QPoint(x+2*s, y+s);
336 diamond[2] = QPoint(x+s, y+2*s);
337 diamond[3] = QPoint(x, y+s);
338 p->setBrush( color );
340 p->drawConvexPolygon( diamond.constData(), 4 );
341 return;
343 case LNONE:
344 return;
345 default:
346 if (!m_item.isEmpty()) {
347 if(listPositionInside()) {
348 //BEGIN HACK
349 #ifdef __GNUC__
350 #warning "KDE4: hack for testregression, remove when main branch"
351 #endif
352 _tx += qMax(-fm.minLeftBearing(), 0);
353 //END HACK
354 if( style()->direction() == LTR) {
355 p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft|Qt::TextDontClip, m_item);
356 p->drawText(_tx + fm.width(m_item), _ty, 0, 0, Qt::AlignLeft|Qt::TextDontClip,
357 QLatin1String(". "));
359 else {
360 const QString& punct(QLatin1String(" ."));
361 p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft|Qt::TextDontClip, punct);
362 p->drawText(_tx + fm.width(punct), _ty, 0, 0, Qt::AlignLeft|Qt::TextDontClip, m_item);
364 } else {
365 if (style()->direction() == LTR) {
366 const QString& punct(QLatin1String(". "));
367 int itemWidth = fm.width(m_item);
368 int punctWidth = fm.width(punct);
369 p->drawText(_tx-offset/2-punctWidth, _ty, 0, 0, Qt::AlignLeft|Qt::TextDontClip, punct);
370 p->drawText(_tx-offset/2-punctWidth-itemWidth, _ty, 0, 0, Qt::AlignLeft|Qt::TextDontClip, m_item);
372 else {
373 //BEGIN HACK
374 #ifdef __GNUC__
375 #warning "KDE4: hack for testregression, remove when main branch"
376 #endif
377 _tx += qMax(-fm.minLeftBearing(), 0);
378 //END HACK
380 const QString& punct(QLatin1String(" ."));
381 p->drawText(_tx+offset/2, _ty, 0, 0, Qt::AlignLeft|Qt::TextDontClip, punct);
382 p->drawText(_tx+offset/2+fm.width(punct), _ty, 0, 0, Qt::AlignLeft|Qt::TextDontClip, m_item);
389 void RenderListMarker::layout()
391 KHTMLAssert( needsLayout() );
393 if ( !minMaxKnown() )
394 calcMinMaxWidth();
396 setNeedsLayout(false);
399 void RenderListMarker::updatePixmap( const QRect& r, CachedImage *o)
401 if(o != m_listImage) {
402 RenderBox::updatePixmap(r, o);
403 return;
406 if(m_width != m_listImage->pixmap_size().width() || m_height != m_listImage->pixmap_size().height())
407 setNeedsLayoutAndMinMaxRecalc();
408 else
409 repaintRectangle(0, 0, m_width, m_height);
412 void RenderListMarker::calcMinMaxWidth()
414 KHTMLAssert( !minMaxKnown() );
416 m_markerWidth = m_width = 0;
418 if(m_listImage && !m_listImage->isErrorImage()) {
419 m_markerWidth = m_listImage->pixmap_size().width() + cMarkerPadding;
420 if (listPositionInside())
421 m_width = m_markerWidth;
422 m_height = m_listImage->pixmap_size().height();
423 m_minWidth = m_maxWidth = m_width;
424 setMinMaxKnown();
425 return;
428 const QFontMetrics &fm = style()->fontMetrics();
429 m_height = fm.ascent();
431 // Skip uncounted elements
432 switch(style()->listStyleType()) {
433 // Glyphs:
434 case LDISC:
435 case LCIRCLE:
436 case LSQUARE:
437 case LBOX:
438 case LDIAMOND:
439 m_markerWidth = fm.ascent();
440 goto end;
441 default:
442 break;
445 { // variable scope
446 CounterNode *counter = m_listItem->m_counter;
447 if (!counter) {
448 counter = m_listItem->getCounter("list-item", true);
449 counter->setRenderer(this);
450 m_listItem->m_counter = counter;
454 assert(counter);
455 int value = counter->count();
456 if (counter->isReset()) value = counter->value();
457 int total = value;
458 if (counter->parent()) total = counter->parent()->total();
460 switch(style()->listStyleType())
462 // Numeric:
463 case LDECIMAL:
464 m_item.setNum ( value );
465 break;
466 case DECIMAL_LEADING_ZERO: {
467 int decimals = 2;
468 int t = total/100;
469 while (t>0) {
470 t = t/10;
471 decimals++;
473 decimals = qMax(decimals, 2);
474 QString num = QString::number(value);
475 m_item.fill('0',decimals-num.length());
476 m_item.append(num);
477 break;
479 case ARABIC_INDIC:
480 m_item = toArabicIndic( value );
481 break;
482 case LAO:
483 m_item = toLao( value );
484 break;
485 case PERSIAN:
486 case URDU:
487 m_item = toPersianUrdu( value );
488 break;
489 case THAI:
490 m_item = toThai( value );
491 break;
492 case TIBETAN:
493 m_item = toTibetan( value );
494 break;
495 // Algoritmic:
496 case LOWER_ROMAN:
497 m_item = toRoman( value, false );
498 break;
499 case UPPER_ROMAN:
500 m_item = toRoman( value, true );
501 break;
502 case HEBREW:
503 m_item = toHebrew( value );
504 break;
505 case ARMENIAN:
506 m_item = toArmenian( value );
507 break;
508 case GEORGIAN:
509 m_item = toGeorgian( value );
510 break;
511 // Alphabetic:
512 case LOWER_ALPHA:
513 case LOWER_LATIN:
514 m_item = toLowerLatin( value );
515 break;
516 case UPPER_ALPHA:
517 case UPPER_LATIN:
518 m_item = toUpperLatin( value );
519 break;
520 case LOWER_GREEK:
521 m_item = toLowerGreek( value );
522 break;
523 case UPPER_GREEK:
524 m_item = toUpperGreek( value );
525 break;
526 case HIRAGANA:
527 m_item = toHiragana( value );
528 break;
529 case HIRAGANA_IROHA:
530 m_item = toHiraganaIroha( value );
531 break;
532 case KATAKANA:
533 m_item = toKatakana( value );
534 break;
535 case KATAKANA_IROHA:
536 m_item = toKatakanaIroha( value );
537 break;
538 // Ideographic:
539 case JAPANESE_FORMAL:
540 m_item = toJapaneseFormal( value );
541 break;
542 case JAPANESE_INFORMAL:
543 m_item = toJapaneseInformal( value );
544 break;
545 case SIMP_CHINESE_FORMAL:
546 m_item = toSimpChineseFormal( value );
547 break;
548 case SIMP_CHINESE_INFORMAL:
549 m_item = toSimpChineseInformal( value );
550 break;
551 case TRAD_CHINESE_FORMAL:
552 m_item = toTradChineseFormal( value );
553 break;
554 case CJK_IDEOGRAPHIC:
555 // CSS 3 List says treat as trad-chinese-informal
556 case TRAD_CHINESE_INFORMAL:
557 m_item = toTradChineseInformal( value );
558 break;
559 // special:
560 case LNONE:
561 break;
562 default:
563 KHTMLAssert(false);
565 m_markerWidth = fm.width(m_item) + fm.width(QLatin1String(". "));
568 end:
569 if(listPositionInside())
570 m_width = m_markerWidth;
572 m_minWidth = m_width;
573 m_maxWidth = m_width;
575 setMinMaxKnown();
578 short RenderListMarker::lineHeight(bool /*b*/) const
580 return height();
583 short RenderListMarker::baselinePosition(bool /*b*/) const
585 return height();
588 void RenderListMarker::calcWidth()
590 RenderBox::calcWidth();
594 int CounterListItem::recount() const
596 static_cast<RenderListItem*>(m_renderer)->m_marker->setNeedsLayoutAndMinMaxRecalc();
599 void CounterListItem::setSelfDirty()
605 #undef BOX_DEBUG