From dfe289850f068f19ba4a83ab4e7e22a7e09c13c9 Mon Sep 17 00:00:00 2001 From: Timothy Pearson Date: Sat, 26 Jan 2013 13:17:21 -0600 Subject: Rename a number of libraries and executables to avoid conflicts with KDE4 --- tdehtml/rendering/render_table.cpp | 3070 ++++++++++++++++++++++++++++++++++++ 1 file changed, 3070 insertions(+) create mode 100644 tdehtml/rendering/render_table.cpp (limited to 'tdehtml/rendering/render_table.cpp') diff --git a/tdehtml/rendering/render_table.cpp b/tdehtml/rendering/render_table.cpp new file mode 100644 index 000000000..ef7fe1031 --- /dev/null +++ b/tdehtml/rendering/render_table.cpp @@ -0,0 +1,3070 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1997 Martin Jones (mjones@kde.org) + * (C) 1997 Torben Weis (weis@kde.org) + * (C) 1998 Waldo Bastian (bastian@kde.org) + * (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2003 Apple Computer, Inc. + * (C) 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. + */ + +//#define TABLE_DEBUG +//#define TABLE_PRINT +//#define DEBUG_LAYOUT +//#define BOX_DEBUG +#include "rendering/render_table.h" +#include "rendering/render_replaced.h" +#include "rendering/render_canvas.h" +#include "rendering/table_layout.h" +#include "html/html_tableimpl.h" +#include "html/html_formimpl.h" +#include "misc/htmltags.h" +#include "misc/htmlattrs.h" +#include "rendering/render_line.h" +#include "xml/dom_docimpl.h" + +#include + +#include +#include + +#include +#include + +using namespace tdehtml; +using namespace DOM; + +RenderTable::RenderTable(DOM::NodeImpl* node) + : RenderBlock(node) +{ + + tCaption = 0; + head = foot = firstBody = 0; + tableLayout = 0; + m_currentBorder = 0; + + has_col_elems = false; + hspacing = vspacing = 0; + padding = 0; + needSectionRecalc = false; + padding = 0; + + columnPos.resize( 2 ); + columnPos.fill( 0 ); + columns.resize( 1 ); + columns.fill( ColumnStruct() ); + + columnPos[0] = 0; +} + +RenderTable::~RenderTable() +{ + delete tableLayout; +} + +void RenderTable::setStyle(RenderStyle *_style) +{ + ETableLayout oldTableLayout = style() ? style()->tableLayout() : TAUTO; + if ( _style->display() == INLINE ) _style->setDisplay( INLINE_TABLE ); + if ( _style->display() != INLINE_TABLE ) _style->setDisplay(TABLE); + if ( !_style->flowAroundFloats() ) _style->setFlowAroundFloats(true); + RenderBlock::setStyle(_style); + + // init RenderObject attributes + setInline(style()->display()==INLINE_TABLE && !isPositioned()); + setReplaced(style()->display()==INLINE_TABLE); + + // In the collapsed border model, there is no cell spacing. + hspacing = collapseBorders() ? 0 : style()->borderHorizontalSpacing(); + vspacing = collapseBorders() ? 0 : style()->borderVerticalSpacing(); + columnPos[0] = hspacing; + + if ( !tableLayout || style()->tableLayout() != oldTableLayout ) { + delete tableLayout; + + // According to the CSS2 spec, you only use fixed table layout if an + // explicit width is specified on the table. Auto width implies auto table layout. + if (style()->tableLayout() == TFIXED && !style()->width().isVariable()) { + tableLayout = new FixedTableLayout(this); +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "using fixed table layout" << endl; +#endif + } else + tableLayout = new AutoTableLayout(this); + } +} + +short RenderTable::lineHeight(bool b) const +{ + // Inline tables are replaced elements. Otherwise, just pass off to + // the base class. + if (isReplaced()) + return height()+marginTop()+marginBottom(); + return RenderBlock::lineHeight(b); +} + +short RenderTable::baselinePosition(bool b) const +{ + // Inline tables are replaced elements. Otherwise, just pass off to + // the base class. + if (isReplaced()) + return height()+marginTop()+marginBottom(); + return RenderBlock::baselinePosition(b); +} + + +void RenderTable::addChild(RenderObject *child, RenderObject *beforeChild) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(Table)::addChild( " << child->renderName() << ", " << + (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; +#endif + bool wrapInAnonymousSection = false; + + switch(child->style()->display()) + { + case TABLE_CAPTION: + if (child->isRenderBlock()) + tCaption = static_cast(child); + break; + case TABLE_COLUMN: + case TABLE_COLUMN_GROUP: + has_col_elems = true; + break; + case TABLE_HEADER_GROUP: + if ( !head ) { + if (child->isTableSection()) + head = static_cast(child); + } + else if ( !firstBody ) + if (child->isTableSection()) + firstBody = static_cast(child); + break; + case TABLE_FOOTER_GROUP: + if ( !foot ) { + if (child->isTableSection()) + foot = static_cast(child); + break; + } + // fall through + case TABLE_ROW_GROUP: + if(!firstBody) + if (child->isTableSection()) + firstBody = static_cast(child); + break; + case TABLE_CELL: + case TABLE_ROW: + wrapInAnonymousSection = true; + break; + case BLOCK: +// case BOX: + case COMPACT: + case INLINE: + case INLINE_BLOCK: +// case INLINE_BOX: + case INLINE_TABLE: + case LIST_ITEM: + case NONE: + case RUN_IN: + case TABLE: + // The special TABLE > FORM quirk allows the form to sit directly under the table + if (child->element() && child->element()->isHTMLElement() && child->element()->id() == ID_FORM) + wrapInAnonymousSection = !static_cast(child->element())->isMalformed(); + else + wrapInAnonymousSection = true; + break; + } + + if (!wrapInAnonymousSection) { + RenderContainer::addChild(child, beforeChild); + return; + } + + if (!beforeChild && lastChild() && lastChild()->isTableSection() && lastChild()->isAnonymous()) { + lastChild()->addChild(child); + return; + } + + RenderObject *lastBox = beforeChild; + RenderObject *nextToLastBox = beforeChild; + while (lastBox && lastBox->parent()->isAnonymous() && + !lastBox->isTableSection() && lastBox->style()->display() != TABLE_CAPTION) { + nextToLastBox = lastBox; + lastBox = lastBox->parent(); + } + if (lastBox && lastBox->isAnonymous()) { + lastBox->addChild(child, nextToLastBox); + return; + } + + if (beforeChild && !beforeChild->isTableSection()) + beforeChild = 0; + RenderTableSection* section = new (renderArena()) RenderTableSection(document() /* anonymous */); + RenderStyle* newStyle = new RenderStyle(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(TABLE_ROW_GROUP); + section->setStyle(newStyle); + addChild(section, beforeChild); + section->addChild(child); +} + +void RenderTable::calcWidth() +{ + if ( isPositioned() ) { + calcAbsoluteHorizontal(); + } + + RenderBlock *cb = containingBlock(); + int availableWidth = cb->lineWidth( m_y ); + + LengthType widthType = style()->width().type(); + if(widthType > Relative && style()->width().value() > 0) { + // Percent or fixed table + // Percent is calculated from contentWidth, not available width + m_width = calcBoxWidth(style()->width().minWidth( cb->contentWidth() )); + } else { + // Subtract out any fixed margins from our available width for auto width tables. + int marginTotal = 0; + if (!style()->marginLeft().isVariable()) + marginTotal += style()->marginLeft().width(availableWidth); + if (!style()->marginRight().isVariable()) + marginTotal += style()->marginRight().width(availableWidth); + + // Subtract out our margins to get the available content width. + int availContentWidth = kMax(0, availableWidth - marginTotal); + + // Ensure we aren't bigger than our max width or smaller than our min width. + m_width = kMin(availContentWidth, m_maxWidth); + } + + m_width = kMax (m_width, m_minWidth); + + // Finally, with our true width determined, compute our margins for real. + m_marginRight=0; + m_marginLeft=0; + + calcHorizontalMargins(style()->marginLeft(),style()->marginRight(),availableWidth); +} + +void RenderTable::layout() +{ + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + KHTMLAssert( !needSectionRecalc ); + + if (posChildNeedsLayout() && !normalChildNeedsLayout() && !selfNeedsLayout()) { + // All we have to is lay out our positioned objects. + layoutPositionedObjects(true); + setNeedsLayout(false); + return; + } + + if (markedForRepaint()) { + repaintDuringLayout(); + setMarkedForRepaint(false); + } + + m_height = 0; + initMaxMarginValues(); + + int oldWidth = m_width; + calcWidth(); + m_overflowWidth = m_width; + + if (tCaption && (oldWidth != m_width || tCaption->style()->height().isPercent())) + tCaption->setChildNeedsLayout(true); + + // the optimization below doesn't work since the internal table + // layout could have changed. we need to add a flag to the table + // layout that tells us if something has changed in the min max + // calculations to do it correctly. +// if ( oldWidth != m_width || columns.size() + 1 != columnPos.size() ) + tableLayout->layout(); + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(Table)::layout1() width=" << width() << ", marginLeft=" << marginLeft() << " marginRight=" << marginRight() << endl; +#endif + + setCellWidths(); + + // layout child objects + int calculatedHeight = 0; + + RenderObject *child = firstChild(); + while( child ) { + // FIXME: What about a form that has a display value that makes it a table section? + if ( child->needsLayout() && !(child->element() && child->element()->id() == ID_FORM) ) + child->layout(); + if ( child->isTableSection() ) { + static_cast(child)->calcRowHeight(); + calculatedHeight += static_cast(child)->layoutRows( 0 ); + } + child = child->nextSibling(); + } + + // ### collapse caption margin + if(tCaption && tCaption->style()->captionSide() != CAPBOTTOM) { + tCaption->setPos(tCaption->marginLeft(), tCaption->marginTop()+m_height); + m_height += tCaption->height() + tCaption->marginTop() + tCaption->marginBottom(); + } + + int bpTop = borderTop() + (collapseBorders() ? 0 : paddingTop()); + int bpBottom = borderBottom() + (collapseBorders() ? 0 : paddingBottom()); + + m_height += bpTop; + + int oldHeight = m_height; + if (isPositioned()) + m_height += calculatedHeight + bpBottom; + calcHeight(); + int newHeight = m_height; + m_height = oldHeight; + + Length h = style()->height(); + int th = -(bpTop + bpBottom); // Tables size as though CSS height includes border/padding. + if (isPositioned()) + th += newHeight; + else if (h.isFixed()) + th += h.value(); + else if (h.isPercent()) + th += calcPercentageHeight(h); + + // layout rows + if ( th > calculatedHeight ) { + // we have to redistribute that height to get the constraint correctly + // just force the first body to the height needed + // ### FIXME This should take height constraints on all table sections into account and distribute + // accordingly. For now this should be good enough + if (firstBody) { + firstBody->calcRowHeight(); + firstBody->layoutRows( th - calculatedHeight ); + } + else if (!style()->htmlHacks()) { + // Completely empty tables (with no sections or anything) should at least honor specified height + // in strict mode. + m_height += th; + } + } + + int bl = borderLeft(); + if (!collapseBorders()) + bl += paddingLeft(); + + // position the table sections + if ( head ) { + head->setPos(bl, m_height); + m_height += head->height(); + } + RenderObject *body = firstBody; + while ( body ) { + if ( body != head && body != foot && body->isTableSection() ) { + body->setPos(bl, m_height); + m_height += body->height(); + } + body = body->nextSibling(); + } + if ( foot ) { + foot->setPos(bl, m_height); + m_height += foot->height(); + } + + m_height += bpBottom; + + if(tCaption && tCaption->style()->captionSide()==CAPBOTTOM) { + tCaption->setPos(tCaption->marginLeft(), tCaption->marginTop()+m_height); + m_height += tCaption->height() + tCaption->marginTop() + tCaption->marginBottom(); + } + + if (canvas()->pagedMode()) { + RenderObject *child = firstChild(); + // relayout taking real position into account + while( child ) { + if ( !(child->element() && child->element()->id() == ID_FORM) ) { + child->setNeedsLayout(true); + child->layout(); + if (child->containsPageBreak()) setContainsPageBreak(true); + if (child->needsPageClear()) setNeedsPageClear(true); + } + child = child->nextSibling(); + } + } + + //kdDebug(0) << "table height: " << m_height << endl; + + // table can be containing block of positioned elements. + // ### only pass true if width or height changed. + layoutPositionedObjects( true ); + + m_overflowHeight = m_height; + + setNeedsLayout(false); +} + +void RenderTable::setCellWidths() +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(Table, this=0x" << this << ")::setCellWidths()" << endl; +#endif + + RenderObject *child = firstChild(); + while( child ) { + if ( child->isTableSection() ) + static_cast(child)->setCellWidths(); + child = child->nextSibling(); + } +} + +void RenderTable::paint( PaintInfo& pI, int _tx, int _ty) +{ + if(needsLayout()) return; + + _tx += xPos(); + _ty += yPos(); + +#ifdef TABLE_PRINT + kdDebug( 6040 ) << "RenderTable::paint() w/h = (" << width() << "/" << height() << ")" << endl; +#endif + if (!overhangingContents() && !isRelPositioned() && !isPositioned()) + { + int os = 2*maximalOutlineSize(pI.phase); + if((_ty > pI.r.y() + pI.r.height() + os) || (_ty + height() < pI.r.y() - os)) return; + if((_tx > pI.r.x() + pI.r.width() + os) || (_tx + width() < pI.r.x() - os)) return; + } + +#ifdef TABLE_PRINT + kdDebug( 6040 ) << "RenderTable::paint(2) " << _tx << "/" << _ty << " (" << _y << "/" << _h << ")" << endl; +#endif + + if (pI.phase == PaintActionOutline) + paintOutline(pI.p, _tx, _ty, width(), height(), style()); + + if(( pI.phase == PaintActionElementBackground || pI.phase == PaintActionChildBackground ) + && shouldPaintBackgroundOrBorder() && style()->visibility() == VISIBLE) + paintBoxDecorations(pI, _tx, _ty); + + if ( pI.phase == PaintActionElementBackground ) + return; + + PaintAction oldphase = pI.phase; + if ( pI.phase == PaintActionChildBackgrounds ) + pI.phase = PaintActionChildBackground; + + for( RenderObject *child = firstChild(); child; child = child->nextSibling()) + if ( child->isTableSection() || child == tCaption ) + child->paint( pI, _tx, _ty ); + + if (collapseBorders() && + (pI.phase == PaintActionElementBackground || pI.phase == PaintActionChildBackground) + && style()->visibility() == VISIBLE) { + // Collect all the unique border styles that we want to paint in a sorted list. Once we + // have all the styles sorted, we then do individual passes, painting each style of border + // from lowest precedence to highest precedence. + pI.phase = PaintActionCollapsedTableBorders; + TQValueList borderStyles; + collectBorders(borderStyles); +#if 0 + TQString m; + for (uint i = 0; i < borderStyles.count(); i++) + m += TQString("%1 ").arg((*borderStyles.at(i)).width()); + kdDebug(6040) << m << endl; +#endif + TQValueListIterator it = borderStyles.begin(); + TQValueListIterator end = borderStyles.end(); + for (; it != end; ++it) { + m_currentBorder = &*it; + for (RenderObject *child = firstChild(); child; child = child->nextSibling()) { + if (child->isTableSection()) + child->paint(pI, _tx, _ty); + } + } + m_currentBorder = 0; + } + + pI.phase = oldphase; +#ifdef BOX_DEBUG + outlineBox(p, _tx, _ty, "blue"); +#endif +} + +void RenderTable::paintBoxDecorations(PaintInfo &pI, int _tx, int _ty) +{ + int w = width(); + int h = height(); + + // Account for the caption. + if (tCaption) { + int captionHeight = (tCaption->height() + tCaption->marginBottom() + tCaption->marginTop()); + h -= captionHeight; + if (tCaption->style()->captionSide() != CAPBOTTOM) + _ty += captionHeight; + } + + int my = kMax(_ty,pI.r.y()); + int mh; + if (_tybackgroundColor(), style()->backgroundLayers(), my, mh, _tx, _ty, w, h); + + if (style()->hasBorder() && !collapseBorders()) + paintBorder(pI.p, _tx, _ty, w, h, style()); +} + +void RenderTable::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + if ( needSectionRecalc ) + recalcSections(); + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(Table " << this << ")::calcMinMaxWidth()" << endl; +#endif + + tableLayout->calcMinMaxWidth(); + + if (tCaption) { + tCaption->calcWidth(); + if (tCaption->marginLeft()+tCaption->marginRight()+tCaption->minWidth() > m_minWidth) + m_minWidth = tCaption->marginLeft()+tCaption->marginRight()+tCaption->minWidth(); + } + + setMinMaxKnown(); +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << " END: (Table " << this << ")::calcMinMaxWidth() min = " << m_minWidth << " max = " << m_maxWidth << endl; +#endif +} + +void RenderTable::close() +{ +// kdDebug( 6040 ) << "RenderTable::close()" << endl; + setNeedsLayoutAndMinMaxRecalc(); +} + +void RenderTable::splitColumn( int pos, int firstSpan ) +{ + // we need to add a new columnStruct + int oldSize = columns.size(); + columns.resize( oldSize + 1 ); + int oldSpan = columns[pos].span; +// tqDebug("splitColumn( %d,%d ), oldSize=%d, oldSpan=%d", pos, firstSpan, oldSize, oldSpan ); + KHTMLAssert( oldSpan > firstSpan ); + columns[pos].span = firstSpan; + memmove( columns.data()+pos+1, columns.data()+pos, (oldSize-pos)*sizeof(ColumnStruct) ); + columns[pos+1].span = oldSpan - firstSpan; + + // change width of all rows. + RenderObject *child = firstChild(); + while ( child ) { + if ( child->isTableSection() ) { + RenderTableSection *section = static_cast(child); + int size = section->grid.size(); + int row = 0; + if ( section->cCol > pos ) + section->cCol++; + while ( row < size ) { + section->grid[row].row->resize( oldSize+1 ); + RenderTableSection::Row &r = *section->grid[row].row; + memmove( r.data()+pos+1, r.data()+pos, (oldSize-pos)*sizeof( RenderTableCell * ) ); +// tqDebug("moving from %d to %d, num=%d", pos, pos+1, (oldSize-pos-1) ); + r[pos+1] = r[pos] ? (RenderTableCell *)-1 : 0; + row++; + } + } + child = child->nextSibling(); + } + columnPos.resize( numEffCols()+1 ); + setNeedsLayoutAndMinMaxRecalc(); +} + +void RenderTable::appendColumn( int span ) +{ + // easy case. + int pos = columns.size(); +// tqDebug("appendColumn( %d ), size=%d", span, pos ); + int newSize = pos + 1; + columns.resize( newSize ); + columns[pos].span = span; + //tqDebug("appending column at %d, span %d", pos, span ); + + // change width of all rows. + RenderObject *child = firstChild(); + while ( child ) { + if ( child->isTableSection() ) { + RenderTableSection *section = static_cast(child); + int size = section->grid.size(); + int row = 0; + while ( row < size ) { + section->grid[row].row->resize( newSize ); + section->cellAt( row, pos ) = 0; + row++; + } + + } + child = child->nextSibling(); + } + columnPos.resize( numEffCols()+1 ); + setNeedsLayoutAndMinMaxRecalc(); +} + +RenderTableCol *RenderTable::colElement( int col ) { + if ( !has_col_elems ) + return 0; + RenderObject *child = firstChild(); + int cCol = 0; + while ( child ) { + if ( child->isTableCol() ) { + RenderTableCol *colElem = static_cast(child); + int span = colElem->span(); + if ( !colElem->firstChild() ) { + cCol += span; + if ( cCol > col ) + return colElem; + } + + RenderObject *next = child->firstChild(); + if ( !next ) + next = child->nextSibling(); + if ( !next && child->parent()->isTableCol() ) + next = child->parent()->nextSibling(); + child = next; + } else if (child == tCaption) { + child = child->nextSibling(); + } else + break; + } + return 0; +} + +void RenderTable::recalcSections() +{ + tCaption = 0; + head = foot = firstBody = 0; + has_col_elems = false; + + RenderObject *child = firstChild(); + // We need to get valid pointers to caption, head, foot and firstbody again + while ( child ) { + switch(child->style()->display()) { + case TABLE_CAPTION: + if ( !tCaption && child->isRenderBlock() ) { + tCaption = static_cast(child); + tCaption->setNeedsLayout(true); + } + break; + case TABLE_COLUMN: + case TABLE_COLUMN_GROUP: + has_col_elems = true; + break; + case TABLE_HEADER_GROUP: + if (child->isTableSection()) { + RenderTableSection *section = static_cast(child); + if (!head) + head = section; + else if (!firstBody) + firstBody = section; + if (section->needCellRecalc) + section->recalcCells(); + } + break; + case TABLE_FOOTER_GROUP: + if (child->isTableSection()) { + RenderTableSection *section = static_cast(child); + if (!foot) + foot = section; + else if (!firstBody) + firstBody = section; + if (section->needCellRecalc) + section->recalcCells(); + } + break; + case TABLE_ROW_GROUP: + if (child->isTableSection()) { + RenderTableSection *section = static_cast(child); + if (!firstBody) + firstBody = section; + if (section->needCellRecalc) + section->recalcCells(); + } + break; + default: + break; + } + child = child->nextSibling(); + } + needSectionRecalc = false; + setNeedsLayout(true); +} + +RenderObject* RenderTable::removeChildNode(RenderObject* child) +{ + setNeedSectionRecalc(); + return RenderContainer::removeChildNode( child ); +} + +int RenderTable::borderLeft() const +{ + if (collapseBorders()) { + // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin, + // but I'm working to get this changed. For now, follow the spec. + return 0; + } + return RenderBlock::borderLeft(); +} + +int RenderTable::borderRight() const +{ + if (collapseBorders()) { + // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin, + // but I'm working to get this changed. For now, follow the spec. + return 0; + } + return RenderBlock::borderRight(); +} + +int RenderTable::borderTop() const +{ + if (collapseBorders()) { + // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin, + // but I'm working to get this changed. For now, follow the spec. + return 0; + } + return RenderBlock::borderTop(); +} + +int RenderTable::borderBottom() const +{ + if (collapseBorders()) { + // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin, + // but I'm working to get this changed. For now, follow the spec. + return 0; + } + return RenderBlock::borderBottom(); +} + +RenderTableSection* RenderTable::sectionAbove(const RenderTableSection* section, bool skipEmptySections) const +{ + if (section == head) + return 0; + RenderObject *prevSection = (section == foot ? lastChild() : const_cast(section))->previousSibling(); + while (prevSection) { + if (prevSection->isTableSection() && prevSection != head && prevSection != foot && (!skipEmptySections || static_cast(prevSection)->numRows())) + break; + prevSection = prevSection->previousSibling(); + } + if (!prevSection && head && (!skipEmptySections || head->numRows())) + prevSection = head; + return static_cast(prevSection); +} + +RenderTableSection* RenderTable::sectionBelow(const RenderTableSection* section, bool skipEmptySections) const +{ + if (section == foot) + return 0; + RenderObject *nextSection = (section == head ? firstChild() : const_cast(section))->nextSibling(); + while (nextSection) { + if (nextSection->isTableSection() && nextSection != head && nextSection != foot && (!skipEmptySections || static_cast(nextSection)->numRows())) + break; + nextSection = nextSection->nextSibling(); + } + if (!nextSection && foot && (!skipEmptySections || foot->numRows())) + nextSection = foot; + return static_cast(nextSection); +} + +RenderTableCell* RenderTable::cellAbove(const RenderTableCell* cell) const +{ + // Find the section and row to look in + int r = cell->row(); + RenderTableSection* section = 0; + int rAbove = 0; + if (r > 0) { + // cell is not in the first row, so use the above row in its own section + section = cell->section(); + rAbove = r-1; + } else { + section = sectionAbove(cell->section(), true); + if (section) + rAbove = section->numRows() - 1; + } + + // Look up the cell in the section's grid, which requires effective col index + if (section) { + int effCol = colToEffCol(cell->col()); + RenderTableCell* aboveCell; + // If we hit a span back up to a real cell. + do { + aboveCell = section->cellAt(rAbove, effCol); + effCol--; + } while (aboveCell == (RenderTableCell *)-1 && effCol >=0); + return (aboveCell == (RenderTableCell *)-1) ? 0 : aboveCell; + } else { + return 0; + } +} + +RenderTableCell* RenderTable::cellBelow(const RenderTableCell* cell) const +{ + // Find the section and row to look in + int r = cell->row() + cell->rowSpan() - 1; + RenderTableSection* section = 0; + int rBelow = 0; + if (r < cell->section()->numRows()-1) { + // The cell is not in the last row, so use the next row in the section. + section = cell->section(); + rBelow= r+1; + } else { + section = sectionBelow(cell->section(), true); + if (section) + rBelow = 0; + } + + // Look up the cell in the section's grid, which requires effective col index + if (section) { + int effCol = colToEffCol(cell->col()); + RenderTableCell* belowCell; + // If we hit a colspan back up to a real cell. + do { + belowCell = section->cellAt(rBelow, effCol); + effCol--; + } while (belowCell == (RenderTableCell *)-1 && effCol >=0); + return (belowCell == (RenderTableCell *)-1) ? 0 : belowCell; + } else { + return 0; + } +} + +RenderTableCell* RenderTable::cellLeft(const RenderTableCell* cell) const +{ + RenderTableSection* section = cell->section(); + int effCol = colToEffCol(cell->col()); + if (effCol == 0) + return 0; + + // If we hit a colspan back up to a real cell. + RenderTableCell* prevCell; + do { + prevCell = section->cellAt(cell->row(), effCol-1); + effCol--; + } while (prevCell == (RenderTableCell *)-1 && effCol >=0); + return (prevCell == (RenderTableCell *)-1) ? 0 : prevCell; +} + +RenderTableCell* RenderTable::cellRight(const RenderTableCell* cell) const +{ + int effCol = colToEffCol(cell->col()+cell->colSpan()); + if (effCol >= numEffCols()) + return 0; + RenderTableCell* result = cell->section()->cellAt(cell->row(), effCol); + return (result == (RenderTableCell*)-1) ? 0 : result; +} + +#ifdef ENABLE_DUMP +void RenderTable::dump(TQTextStream &stream, const TQString &ind) const +{ + RenderBlock::dump(stream, ind); + + if (tCaption) + stream << " tCaption"; + if (head) + stream << " head"; + if (foot) + stream << " foot"; + + stream << " [cspans:"; + for ( unsigned int i = 0; i < columns.size(); i++ ) + stream << " " << columns[i].span; + stream << "]"; +} + +#endif + +FindSelectionResult RenderTable::checkSelectionPoint( int _x, int _y, int _tx, int _ty, DOM::NodeImpl*& node, int & offset, SelPointState &state ) +{ + int off = offset; + DOM::NodeImpl* nod = node; + + FindSelectionResult pos; + TableSectionIterator it(this); + for (; *it; ++it) { + pos = (*it)->checkSelectionPoint(_x, _y, _tx + m_x, _ty + m_y, nod, off, state); + switch(pos) { + case SelectionPointBeforeInLine: + case SelectionPointInside: + //kdDebug(6030) << "RenderTable::checkSelectionPoint " << this << " returning SelectionPointInside offset=" << offset << endl; + node = nod; + offset = off; + return SelectionPointInside; + case SelectionPointBefore: + //x,y is before this element -> stop here + if ( state.m_lastNode ) { + node = state.m_lastNode; + offset = state.m_lastOffset; + //kdDebug(6030) << "RenderTable::checkSelectionPoint " << this << " before this child " + // << node << "-> returning SelectionPointInside, offset=" << offset << endl; + return SelectionPointInside; + } else { + node = nod; + offset = off; + //kdDebug(6030) << "RenderTable::checkSelectionPoint " << this << " before us -> returning SelectionPointBefore " << node << "/" << offset << endl; + return SelectionPointBefore; + } + break; + case SelectionPointAfter: + if (state.m_afterInLine) break; + // fall through + case SelectionPointAfterInLine: + if (pos == SelectionPointAfterInLine) state.m_afterInLine = true; + //kdDebug(6030) << "RenderTable::checkSelectionPoint: selection after: " << nod << " offset: " << off << " afterInLine: " << state.m_afterInLine << endl; + state.m_lastNode = nod; + state.m_lastOffset = off; + // No "return" here, obviously. We must keep looking into the children. + break; + } + } + // If we are after the last child, return lastNode/lastOffset + // But lastNode can be 0L if there is no child, for instance. + if ( state.m_lastNode ) + { + node = state.m_lastNode; + offset = state.m_lastOffset; + } + // Fallback + return SelectionPointAfter; +} + +// -------------------------------------------------------------------------- + +RenderTableSection::RenderTableSection(DOM::NodeImpl* node) + : RenderBox(node) +{ + // init RenderObject attributes + setInline(false); // our object is not Inline + cCol = 0; + cRow = -1; + needCellRecalc = false; +} + +RenderTableSection::~RenderTableSection() +{ + clearGrid(); +} + +void RenderTableSection::detach() +{ + // recalc cell info because RenderTable has unguarded pointers + // stored that point to this RenderTableSection. + if (table()) + table()->setNeedSectionRecalc(); + + RenderBox::detach(); +} + +void RenderTableSection::setStyle(RenderStyle* _style) +{ + // we don't allow changing this one + if (style()) + _style->setDisplay(style()->display()); + else if (_style->display() != TABLE_FOOTER_GROUP && _style->display() != TABLE_HEADER_GROUP) + _style->setDisplay(TABLE_ROW_GROUP); + + RenderBox::setStyle(_style); +} + +void RenderTableSection::addChild(RenderObject *child, RenderObject *beforeChild) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(TableSection)::addChild( " << child->renderName() << ", beforeChild=" << + (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; +#endif + if ( !child->isTableRow() ) { + // TBODY > FORM quirk (???) + if (child->element() && child->element()->isHTMLElement() && child->element()->id() == ID_FORM && + static_cast(child->element())->isMalformed()) + { + RenderContainer::addChild(child, beforeChild); + return; + } + + RenderObject* last = beforeChild; + if (!last) + last = lastChild(); + if (last && last->isAnonymous()) { + last->addChild(child); + return; + } + + // If beforeChild is inside an anonymous cell/row, insert into the cell or into + // the anonymous row containing it, if there is one. + RenderObject* lastBox = last; + while (lastBox && lastBox->parent()->isAnonymous() && !lastBox->isTableRow()) + lastBox = lastBox->parent(); + if (lastBox && lastBox->isAnonymous()) { + lastBox->addChild(child, beforeChild); + return; + } + + RenderObject* row = new (renderArena()) RenderTableRow(document() /* anonymous table */); + RenderStyle* newStyle = new RenderStyle(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(TABLE_ROW); + row->setStyle(newStyle); + addChild(row, beforeChild); + row->addChild(child); + return; + } + + if (beforeChild) + setNeedCellRecalc(); + + cRow++; + cCol = 0; + + ensureRows( cRow+1 ); + KHTMLAssert( child->isTableRow() ); + grid[cRow].rowRenderer = static_cast(child); + + if (!beforeChild) { + grid[cRow].height = child->style()->height(); + if ( grid[cRow].height.isRelative() ) + grid[cRow].height = Length(); + } + + + RenderContainer::addChild(child,beforeChild); +} + +void RenderTableSection::ensureRows( int numRows ) +{ + int nRows = grid.size(); + int nCols = table()->numEffCols(); + if ( numRows > nRows ) { + grid.resize( numRows ); + for ( int r = nRows; r < numRows; r++ ) { + grid[r].row = new Row( nCols ); + grid[r].row->fill( 0 ); + grid[r].rowRenderer = 0; + grid[r].baseLine = 0; + grid[r].height = Length(); + } + } + +} + +void RenderTableSection::addCell( RenderTableCell *cell, RenderTableRow *row ) +{ + int rSpan = cell->rowSpan(); + int cSpan = cell->colSpan(); + TQMemArray &columns = table()->columns; + int nCols = columns.size(); + + // ### mozilla still seems to do the old HTML way, even for strict DTD + // (see the annotation on table cell layouting in the CSS specs and the testcase below: + // + //
1 2 3 4 + //
5 + //
+ while ( cCol < nCols && cellAt( cRow, cCol ) ) + cCol++; + +// tqDebug("adding cell at %d/%d span=(%d/%d)", cRow, cCol, rSpan, cSpan ); + + if ( rSpan == 1 ) { + // we ignore height settings on rowspan cells + Length height = cell->style()->height(); + if ( height.value() > 0 || (height.isRelative() && height.value() >= 0) ) { + Length cRowHeight = grid[cRow].height; + switch( height.type() ) { + case Percent: + if ( !cRowHeight.isPercent() || + (cRowHeight.isPercent() && cRowHeight.value() < height.value() ) ) + grid[cRow].height = height; + break; + case Fixed: + if ( cRowHeight.type() < Percent || + ( cRowHeight.isFixed() && cRowHeight.value() < height.value() ) ) + grid[cRow].height = height; + break; + case Relative: +#if 0 + // we treat this as variable. This is correct according to HTML4, as it only specifies length for the height. + if ( cRowHeight.type == Variable || + ( cRowHeight.type == Relative && cRowHeight.value < height.value ) ) + grid[cRow].height = height; + break; +#endif + default: + break; + } + } + } + + // make sure we have enough rows + ensureRows( cRow + rSpan ); + + grid[cRow].rowRenderer = row; + + int col = cCol; + // tell the cell where it is + RenderTableCell *set = cell; + while ( cSpan ) { + int currentSpan; + if ( cCol >= nCols ) { + table()->appendColumn( cSpan ); + currentSpan = cSpan; + } else { + if ( cSpan < columns[cCol].span ) + table()->splitColumn( cCol, cSpan ); + currentSpan = columns[cCol].span; + } + int r = 0; + while ( r < rSpan ) { + if ( !cellAt( cRow + r, cCol ) ) { +// tqDebug(" adding cell at %d, %d", cRow + r, cCol ); + cellAt( cRow + r, cCol ) = set; + } + r++; + } + cCol++; + cSpan -= currentSpan; + set = (RenderTableCell *)-1; + } + if ( cell ) { + cell->setRow( cRow ); + cell->setCol( table()->effColToCol( col ) ); + } +} + + + +void RenderTableSection::setCellWidths() +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(Table, this=0x" << this << ")::setCellWidths()" << endl; +#endif + TQMemArray &columnPos = table()->columnPos; + + int rows = grid.size(); + for ( int i = 0; i < rows; i++ ) { + Row &row = *grid[i].row; + int cols = row.size(); + for ( int j = 0; j < cols; j++ ) { + RenderTableCell *cell = row[j]; +// tqDebug("cell[%d,%d] = %p", i, j, cell ); + if ( !cell || cell == (RenderTableCell *)-1 ) + continue; + int endCol = j; + int cspan = cell->colSpan(); + while ( cspan && endCol < cols ) { + cspan -= table()->columns[endCol].span; + endCol++; + } + int w = columnPos[endCol] - columnPos[j] - table()->borderHSpacing(); +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "setting width of cell " << cell << " " << cell->row() << "/" << cell->col() << " to " << w << " colspan=" << cell->colSpan() << " start=" << j << " end=" << endCol << endl; +#endif + int oldWidth = cell->width(); + if ( w != oldWidth ) { + cell->setNeedsLayout(true); + cell->setWidth( w ); + } + } + } +} + +short RenderTableSection::width() const +{ + return table()->width(); +} + + +void RenderTableSection::calcRowHeight() +{ + int indx; + RenderTableCell *cell; + + int totalRows = grid.size(); + int vspacing = table()->borderVSpacing(); + + rowPos.resize( totalRows + 1 ); + rowPos[0] = vspacing + borderTop(); + + for ( int r = 0; r < totalRows; r++ ) { + rowPos[r+1] = 0; + + int baseline=0; + int bdesc = 0; +// tqDebug("height of row %d is %d/%d", r, grid[r].height.value, grid[r].height.type ); + int ch = grid[r].height.minWidth( 0 ); + int pos = rowPos[r] + ch + (grid[r].rowRenderer ? vspacing : 0); + + if ( pos > rowPos[r+1] ) + rowPos[r+1] = pos; + + Row *row = grid[r].row; + int totalCols = row->size(); + int totalRows = grid.size(); + bool pagedMode = canvas()->pagedMode(); + + grid[r].needFlex = false; + + for ( int c = 0; c < totalCols; c++ ) { + cell = cellAt(r, c); + if ( !cell || cell == (RenderTableCell *)-1 ) + continue; + if ( r < totalRows - 1 && cellAt(r+1, c) == cell ) + continue; + + if ( ( indx = r - cell->rowSpan() + 1 ) < 0 ) + indx = 0; + + if (cell->cellPercentageHeight() != -1) { + cell->setCellPercentageHeight(-1); + cell->setChildNeedsLayout(true, false); + if (cell->hasFlexedAnonymous()) { + for (RenderObject* o = cell->firstChild(); o ; o = o->nextSibling()) + if (o->isAnonymousBlock()) + o->setChildNeedsLayout(true, false); + } + if (pagedMode) cell->setNeedsLayout(true); + cell->layoutIfNeeded(); + } + + ch = cell->style()->height().width(0); + if ( cell->height() > ch) + ch = cell->height(); + + if (!cell->style()->height().isVariable()) + grid[r].needFlex = true; + + pos = rowPos[indx] + ch + (grid[r].rowRenderer ? vspacing : 0); + + if ( pos > rowPos[r+1] ) + rowPos[r+1] = pos; + + // find out the baseline + EVerticalAlign va = cell->style()->verticalAlign(); + if (va == BASELINE || va == TEXT_BOTTOM || va == TEXT_TOP + || va == SUPER || va == SUB) + { + int b=cell->baselinePosition(); + if (b > cell->borderTop() + cell->paddingTop()) { + if (b>baseline) + baseline=b; + + int td = rowPos[ indx ] + ch - b; + if (td>bdesc) + bdesc = td; + } + } + } + + //do we have baseline aligned elements? + if (baseline) { + // increase rowheight if baseline requires + int bRowPos = baseline + bdesc + (grid[r].rowRenderer ? vspacing : 0); + if (rowPos[r+1]borderHSpacing(); + int vspacing = table()->borderVSpacing(); + + // Set the width of our section now. The rows will also be this width. + m_width = table()->contentWidth(); + + if (markedForRepaint()) { + repaintDuringLayout(); + setMarkedForRepaint(false); + } + + if (toAdd && totalRows && (rowPos[totalRows] || !nextSibling())) { + + int totalHeight = rowPos[totalRows] + toAdd; +// tqDebug("layoutRows: totalHeight = %d", totalHeight ); + + int dh = toAdd; + int totalPercent = 0; + int numVariable = 0; + for ( int r = 0; r < totalRows; r++ ) { + if ( grid[r].height.isVariable() && !emptyRow(r)) + numVariable++; + else if ( grid[r].height.isPercent() ) + totalPercent += grid[r].height.value(); + } + if ( totalPercent ) { +// tqDebug("distributing %d over percent rows totalPercent=%d", dh, totalPercent ); + // try to satisfy percent + int add = 0; + if ( totalPercent > 100 ) + totalPercent = 100; + int rh = rowPos[1]-rowPos[0]; + for ( int r = 0; r < totalRows; r++ ) { + if ( totalPercent > 0 && grid[r].height.isPercent() ) { + int toAdd = kMin( dh, (totalHeight * grid[r].height.value() / 100)-rh ); + // If toAdd is negative, then we don't want to shrink the row (this bug + // affected Outlook Web Access). + toAdd = kMax(0, toAdd); + add += toAdd; + dh -= toAdd; + totalPercent -= grid[r].height.value(); +// tqDebug( "adding %d to row %d", toAdd, r ); + } + if ( r < totalRows-1 ) + rh = rowPos[r+2] - rowPos[r+1]; + rowPos[r+1] += add; + } + } + if ( numVariable ) { + // distribute over non-empty variable rows +// tqDebug("distributing %d over variable rows numVariable=%d", dh, numVariable ); + int add = 0; + int toAdd = dh/numVariable; + for ( int r = 0; r < totalRows; r++ ) { + if ( grid[r].height.isVariable() && !emptyRow(r)) { + add += toAdd; + } + rowPos[r+1] += add; + } + dh -= add; + } + if (dh>0 && rowPos[totalRows]) { + // if some left overs, distribute weighted. + int tot=rowPos[totalRows]; + int add=0; + int prev=rowPos[0]; + for ( int r = 0; r < totalRows; r++ ) { + //weight with the original height + add+=dh*(rowPos[r+1]-prev)/tot; + prev=rowPos[r+1]; + rowPos[r+1]+=add; + } + dh -= add; + } + if (dh > totalRows) { + // distribute to tables with all empty rows + int add=0; + int toAdd = dh/totalRows; + for ( int r = 0; r < totalRows; r++ ) { + add += toAdd; + rowPos[r+1] += add; + } + dh -= add; + } + // Finally distribute round-off values + if (dh > 0) { + // There is not enough for every row + int add=0; + for ( int r = 0; r < totalRows; r++ ) { + if (add < dh) add++; + rowPos[r+1] += add; + } + dh -= add; + } + assert (dh == 0); + } + + int leftOffset = borderLeft() + hspacing; + + int nEffCols = table()->numEffCols(); + for ( int r = 0; r < totalRows; r++ ) + { + Row *row = grid[r].row; + int totalCols = row->size(); + +#ifdef APPLE_CHANGES + // in WC, rows and cells share the same coordinate space, so that rows can have + // dimensions in the layer system. This is of dubious value, and a heavy maintenance burden + // (RenderObject's coordinates can't be used deterministically anymore) so we'll consider other options. + + // Set the row's x/y position and width/height. + if (grid[r].rowRenderer) { + grid[r].rowRenderer->setPos(0, rowPos[r]); + grid[r].rowRenderer->setWidth(m_width); + grid[r].rowRenderer->setHeight(rowPos[r+1] - rowPos[r] - vspacing); + } +#endif + + for ( int c = 0; c < nEffCols; c++ ) + { + RenderTableCell *cell = cellAt(r, c); + if (!cell || cell == (RenderTableCell *)-1 ) + continue; + if ( r < totalRows - 1 && cell == cellAt(r+1, c) ) + continue; + + if ( ( rindx = r-cell->rowSpan()+1 ) < 0 ) + rindx = 0; + + rHeight = rowPos[r+1] - rowPos[rindx] - vspacing; + + // Force percent height children to lay themselves out again. + // This will cause, e.g., textareas to grow to + // fill the area. FIXME:
s and blocks still don't + // work right. We'll need to have an efficient way of + // invalidating all percent height objects in a render subtree. + // For now, we just handle immediate children. -dwh + + bool flexAllChildren = grid[r].needFlex || (!table()->style()->height().isVariable() && rHeight != cell->height()); + cell->setHasFlexedAnonymous(false); + if ( flexAllChildren && flexCellChildren(cell) ) { + cell->setCellPercentageHeight(kMax(0, + rHeight - cell->borderTop() - cell->paddingTop() - + cell->borderBottom() - cell->paddingBottom())); + cell->layoutIfNeeded(); + + } + { +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "setting position " << r << "/" << c << ": " + << table()->columnPos[c] /*+ padding */ << "/" << rowPos[rindx] << " height=" << rHeight<< endl; +#endif + + EVerticalAlign va = cell->style()->verticalAlign(); + int te=0; + switch (va) + { + case SUB: + case SUPER: + case TEXT_TOP: + case TEXT_BOTTOM: + case BASELINE: + te = getBaseline(r) - cell->baselinePosition() ; + break; + case TOP: + te = 0; + break; + case MIDDLE: + te = (rHeight - cell->height())/2; + break; + case BOTTOM: + te = rHeight - cell->height(); + break; + default: + break; + } + te = kMax( 0, te ); +#ifdef DEBUG_LAYOUT + // kdDebug( 6040 ) << "CELL " << cell << " te=" << te << ", be=" << rHeight - cell->height() - te << ", rHeight=" << rHeight << ", valign=" << va << endl; +#endif + cell->setCellTopExtra( te ); + cell->setCellBottomExtra( rHeight - cell->height() - te); + } + if (style()->direction()==RTL) { + cell->setPos( + table()->columnPos[(int)totalCols] - + table()->columnPos[table()->colToEffCol(cell->col()+cell->colSpan())] + + leftOffset, + rowPos[rindx] ); + } else { + cell->setPos( table()->columnPos[c] + leftOffset, rowPos[rindx] ); + } + } + } + + m_height = rowPos[totalRows]; + return m_height; +} + +bool RenderTableSection::flexCellChildren(RenderObject* p) const +{ + if (!p) + return false; + RenderObject* o = p->firstChild(); + bool didFlex = false; + while (o) { + if (!o->isText() && o->style()->height().isPercent()) { + if (o->isWidget()) { + // cancel resizes from transitory relayouts + static_cast(o)->cancelPendingResize(); + } + o->setNeedsLayout(true, false); + p->setChildNeedsLayout(true, false); + didFlex = true; + } else if (o->isAnonymousBlock() && flexCellChildren( o )) { + p->setChildNeedsLayout(true, false); + if (p->isTableCell()) + static_cast(p)->setHasFlexedAnonymous(); + didFlex = true; + } + o = o->nextSibling(); + } + return didFlex; +} + +inline static RenderTableRow *firstTableRow(RenderObject *row) +{ + while (row && !row->isTableRow()) + row = row->nextSibling(); + return static_cast(row); +} + +inline static RenderTableRow *nextTableRow(RenderObject *row) +{ + row = row ? row->nextSibling() : row; + while (row && !row->isTableRow()) + row = row->nextSibling(); + return static_cast(row); +} + +int RenderTableSection::lowestPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int bottom = RenderBox::lowestPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return bottom; + + for (RenderObject *row = firstChild(); row; row = row->nextSibling()) { + for (RenderObject *cell = row->firstChild(); cell; cell = cell->nextSibling()) + if (cell->isTableCell()) { + int bp = cell->yPos() + cell->lowestPosition(false); + bottom = kMax(bottom, bp); + } + } + + return bottom; +} + +int RenderTableSection::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int right = RenderBox::rightmostPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return right; + + for (RenderObject *row = firstChild(); row; row = row->nextSibling()) { + for (RenderObject *cell = row->firstChild(); cell; cell = cell->nextSibling()) + if (cell->isTableCell()) { + int rp = cell->xPos() + cell->rightmostPosition(false); + right = kMax(right, rp); + } + } + + return right; +} + +int RenderTableSection::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int left = RenderBox::leftmostPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return left; + + for (RenderObject *row = firstChild(); row; row = row->nextSibling()) { + for (RenderObject *cell = row->firstChild(); cell; cell = cell->nextSibling()) + if (cell->isTableCell()) { + int lp = cell->xPos() + cell->leftmostPosition(false); + left = kMin(left, lp); + } + } + + return left; +} + +int RenderTableSection::highestPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int top = RenderBox::highestPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return top; + + for (RenderObject *row = firstChild(); row; row = row->nextSibling()) { + for (RenderObject *cell = row->firstChild(); cell; cell = cell->nextSibling()) + if (cell->isTableCell()) { + int hp = cell->yPos() + cell->highestPosition(false); + top = kMin(top, hp); + } + } + + return top; +} + +// Search from first_row to last_row for the row containing y +static unsigned int findRow(unsigned int first_row, unsigned int last_row, + const TQMemArray &rowPos, int y) +{ + unsigned int under = first_row; + unsigned int over = last_row; + int offset = (over - under)/2; + while (over-under > 1) { + if (rowPos[under+offset] <= y) + under = under+offset; + else + over = under+offset; + offset = (over - under)/2; + } + + assert(under == first_row || rowPos[under] <= y); + assert(over == last_row || rowPos[over] > y); + + return under; +} + +static void findRowCover(unsigned int &startrow, unsigned int &endrow, + const TQMemArray &rowPos, + int min_y, int max_y) +{ + assert(max_y >= min_y); + unsigned int totalRows = endrow; + + unsigned int index = 0; + // Initial binary search boost: + if (totalRows >= 8) { + int offset = (endrow - startrow)/2; + while (endrow - startrow > 1) { + index = startrow+offset; + if (rowPos[index] <= min_y ) + // index is below both min_y and max_y + startrow = index; + else + if (rowPos[index] > max_y) + // index is above both min_y and max_y + endrow = index; + else + // index is within the selection + break; + offset = (endrow - startrow)/2; + } + } + + // Binary search for startrow + startrow = findRow(startrow, endrow, rowPos, min_y); + // Binary search for endrow + endrow = findRow(startrow, endrow, rowPos, max_y) + 1; + if (endrow > totalRows) endrow = totalRows; +} + +void RenderTableSection::paint( PaintInfo& pI, int tx, int ty ) +{ + unsigned int totalRows = grid.size(); + unsigned int totalCols = table()->columns.size(); + + tx += m_x; + ty += m_y; + + if (pI.phase == PaintActionOutline) + paintOutline(pI.p, tx, ty, width(), height(), style()); + + CollapsedBorderValue *cbs = table()->currentBorderStyle(); + int cbsw2 = cbs ? cbs->width()/2 : 0; + int cbsw21 = cbs ? (cbs->width()+1)/2 : 0; + + int x = pI.r.x(), y = pI.r.y(), w = pI.r.width(), h = pI.r.height(); + // check which rows and cols are visible and only paint these + int os = 2*maximalOutlineSize(pI.phase); + unsigned int startrow = 0; + unsigned int endrow = totalRows; + findRowCover(startrow, endrow, rowPos, y - os - ty - kMax(cbsw21, os), y + h + os - ty + kMax(cbsw2, os)); + + // A binary search is probably not worthwhile for coloumns + unsigned int startcol = 0; + unsigned int endcol = totalCols; + if ( style()->direction() == LTR ) { + for ( ; startcol < totalCols; startcol++ ) { + if ( tx + table()->columnPos[startcol+1] + kMax(cbsw21, os) > x - os ) + break; + } + for ( ; endcol > 0; endcol-- ) { + if ( tx + table()->columnPos[endcol-1] - kMax(cbsw2, os) < x + w + os ) + break; + } + } + if ( startcol < endcol ) { + // draw the cells + for ( unsigned int r = startrow; r < endrow; r++ ) { + // paint the row + if (grid[r].rowRenderer) { + int height = rowPos[r+1] - rowPos[r] - table()->borderVSpacing(); + grid[r].rowRenderer->paintRow(pI, tx, ty + rowPos[r], width(), height); + } + + unsigned int c = startcol; + Row *row = grid[r].row; + Row *nextrow = (r < endrow - 1) ? grid[r+1].row : 0; + // since a cell can be -1 (indicating a colspan) we might have to search backwards to include it + while ( c && (*row)[c] == (RenderTableCell *)-1 ) + c--; + for ( ; c < endcol; c++ ) { + RenderTableCell *cell = (*row)[c]; + if ( !cell || cell == (RenderTableCell *)-1 || nextrow && (*nextrow)[c] == cell ) + continue; +#ifdef TABLE_PRINT + kdDebug( 6040 ) << "painting cell " << r << "/" << c << endl; +#endif + if (pI.phase == PaintActionElementBackground || pI.phase == PaintActionChildBackground) { + // We need to handle painting a stack of backgrounds. This stack (from bottom to top) consists of + // the column group, column, row group, row, and then the cell. + RenderObject* col = table()->colElement(c); + RenderObject* colGroup = 0; + if (col) { + RenderStyle *style = col->parent()->style(); + if (style->display() == TABLE_COLUMN_GROUP) + colGroup = col->parent(); + } + RenderObject* row = cell->parent(); + + // ### + // Column groups and columns first. + // FIXME: Columns and column groups do not currently support opacity, and they are being painted "too late" in + // the stack, since we have already opened a transparency layer (potentially) for the table row group. + // Note that we deliberately ignore whether or not the cell has a layer, since these backgrounds paint "behind" the + // cell. + cell->paintBackgroundsBehindCell(pI, tx, ty, colGroup); + cell->paintBackgroundsBehindCell(pI, tx, ty, col); + + // Paint the row group next. + cell->paintBackgroundsBehindCell(pI, tx, ty, this); + + // Paint the row next, but only if it doesn't have a layer. If a row has a layer, it will be responsible for + // painting the row background for the cell. + if (!row->layer()) + cell->paintBackgroundsBehindCell(pI, tx, ty, row); + } + + if ((!cell->layer() && !cell->parent()->layer()) || pI.phase == PaintActionCollapsedTableBorders) + cell->paint(pI, tx, ty); + } + } + } +} + +void RenderTableSection::recalcCells() +{ + cCol = 0; + cRow = -1; + clearGrid(); + grid.resize( 0 ); + + for (RenderObject *row = firstChild(); row; row = row->nextSibling()) { + if (row->isTableRow()) { + cRow++; + cCol = 0; + ensureRows( cRow+1 ); + grid[cRow].rowRenderer = static_cast(row); + + for (RenderObject *cell = row->firstChild(); cell; cell = cell->nextSibling()) + if (cell->isTableCell()) + addCell( static_cast(cell), static_cast(row) ); + } + } + needCellRecalc = false; + setNeedsLayout(true); +} + +void RenderTableSection::clearGrid() +{ + int rows = grid.size(); + while ( rows-- ) { + delete grid[rows].row; + } +} + +bool RenderTableSection::emptyRow(int rowNum) { + Row &r = *grid[rowNum].row; + const int s = r.size(); + RenderTableCell *cell; + for(int i=0; icanClear(this, level); +} + +void RenderTableSection::addSpaceAt(int pos, int dy) +{ + const int nEffCols = table()->numEffCols(); + const int totalRows = numRows(); + for ( int r = 0; r < totalRows; r++ ) { + if (rowPos[r] >= pos) { + rowPos[r] += dy; + int rindx; + for ( int c = 0; c < nEffCols; c++ ) + { + RenderTableCell *cell = cellAt(r, c); + if (!cell || cell == (RenderTableCell *)-1 ) + continue; + if ( r > 0 && cell == cellAt(r-1, c) ) + continue; + + if ( ( rindx = r-cell->rowSpan()+1 ) < 0 ) + rindx = 0; + + cell->setPos(cell->xPos(), rowPos[r]); + } + } + } + if (rowPos[totalRows] >= pos) + rowPos[totalRows] += dy; + m_height = rowPos[totalRows]; + + setContainsPageBreak(true); +} + + +#ifdef ENABLE_DUMP +void RenderTableSection::dump(TQTextStream &stream, const TQString &ind) const +{ + RenderContainer::dump(stream,ind); + + stream << " grid=(" << grid.size() << "," << table()->numEffCols() << ")"; + for ( unsigned int r = 0; r < grid.size(); r++ ) { + for ( int c = 0; c < table()->numEffCols(); c++ ) { + if ( cellAt( r, c ) && cellAt( r, c ) != (RenderTableCell *)-1 ) + stream << " (" << cellAt( r, c )->row() << "," << cellAt( r, c )->col() << "," + << cellAt(r, c)->rowSpan() << "," << cellAt(r, c)->colSpan() << ") "; + else + stream << " null cell"; + } + } +} +#endif + +static RenderTableCell *seekCell(RenderTableSection *section, int row, int col) +{ + if (row < 0 || col < 0 || row >= section->numRows()) return 0; + // since a cell can be -1 (indicating a colspan) we might have to search backwards to include it + while ( col && section->cellAt( row, col ) == (RenderTableCell *)-1 ) + col--; + + return section->cellAt(row, col); +} + +/** Looks for the first element suitable for text selection, beginning from + * the last. + * @param base search is restricted within this node. This node must have + * a renderer. + * @return the element or @p base if no suitable element found. + */ +static NodeImpl *findLastSelectableNode(NodeImpl *base) +{ + NodeImpl *last = base; + // Look for last text/cdata node that has a renderer, + // or last childless replaced element + while ( last && !(last->renderer() + && ((last->nodeType() == Node::TEXT_NODE || last->nodeType() == Node::CDATA_SECTION_NODE) + || (last->renderer()->isReplaced() && !last->renderer()->lastChild())))) + { + NodeImpl *next = last->lastChild(); + if ( !next ) next = last->previousSibling(); + while ( last && last != base && !next ) + { + last = last->parentNode(); + if ( last && last != base ) + next = last->previousSibling(); + } + last = next; + } + + return last ? last : base; +} + +FindSelectionResult RenderTableSection::checkSelectionPoint( int _x, int _y, int _tx, int _ty, DOM::NodeImpl*& node, int & offset, SelPointState &state ) +{ + // Table sections need extra treatment for selections. The rows are scanned + // from top to bottom, and within each row, only the cell that matches + // the given position best is descended into. + + unsigned int totalRows = grid.size(); + unsigned int totalCols = table()->columns.size(); + +// absolutePosition(_tx, _ty, false); + + _tx += m_x; + _ty += m_y; + +// bool save_last = false; // true to save last investigated cell + + if (needsLayout() || _y < _ty) return SelectionPointBefore; +// else if (_y >= _ty + height()) save_last = true; + + // Find the row containing the pointer + int row_idx = findRow(0, totalRows, rowPos, _y - _ty); + + int col_idx; + if ( style()->direction() == LTR ) { + for ( col_idx = (int)totalCols - 1; col_idx >= 0; col_idx-- ) { + if ( _tx + table()->columnPos[col_idx] < _x ) + break; + } + if (col_idx < 0) col_idx = 0; + } else { + for ( col_idx = 0; col_idx < (int)totalCols; col_idx++ ) { + if ( _tx + table()->columnPos[col_idx] > _x ) + break; + } + if (col_idx >= (int)totalCols) col_idx = (int)totalCols + 1; + } + + FindSelectionResult pos = SelectionPointBefore; + + RenderTableCell *cell = seekCell(this, row_idx, col_idx); + // ### dunno why cell can be 0, maybe due to weird spans? (LS) + if (cell) { + SelPointState localState; + pos = cell->checkSelectionPoint(_x, _y, _tx, _ty, node, offset, localState); + } + + if (pos != SelectionPointBefore) return pos; + + // store last column of last line + row_idx--; + col_idx = totalCols - 1; + cell = seekCell(this, row_idx, col_idx); + + // end of section? take previous section + RenderTableSection *sec = this; + if (!cell) { + sec = *--TableSectionIterator(sec); + if (!sec) return pos; + + cell = seekCell(sec, sec->grid.size() - 1, col_idx); + if (!cell) return pos; + } + + // take last child of previous cell, and store this one as last node + NodeImpl *element = cell->element(); + if (!element) return SelectionPointBefore; + + element = findLastSelectableNode(element); + + state.m_lastNode = element; + state.m_lastOffset = element->maxOffset(); + return SelectionPointBefore; +} + +// Hit Testing +bool RenderTableSection::nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction action, bool inside) +{ + // Table sections cannot ever be hit tested. Effectively they do not exist. + // Just forward to our children always. + tx += m_x; + ty += m_y; + + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { + // FIXME: We have to skip over inline flows, since they can show up inside table rows + // at the moment (a demoted inline
for example). If we ever implement a + // table-specific hit-test method (which we should do for performance reasons anyway), + // then we can remove this check. + if (!child->layer() && !child->isInlineFlow() && child->nodeAtPoint(info, x, y, tx, ty, action, inside)) { + return true; + } + } + + return false; +} + + +// ------------------------------------------------------------------------- + +RenderTableRow::RenderTableRow(DOM::NodeImpl* node) + : RenderContainer(node) +{ + // init RenderObject attributes + setInline(false); // our object is not Inline +} + +RenderObject* RenderTableRow::removeChildNode(RenderObject* child) +{ + RenderTableSection *s = section(); + if (s) + s->setNeedCellRecalc(); + + return RenderContainer::removeChildNode( child ); +} + +void RenderTableRow::detach() +{ + RenderTableSection *s = section(); + if (s) + s->setNeedCellRecalc(); + + RenderContainer::detach(); +} + +void RenderTableRow::setStyle(RenderStyle* newStyle) +{ + if (section() && style() && style()->height() != newStyle->height()) + section()->setNeedCellRecalc(); + + newStyle->setDisplay(TABLE_ROW); + RenderContainer::setStyle(newStyle); +} + +void RenderTableRow::addChild(RenderObject *child, RenderObject *beforeChild) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(TableRow)::addChild( " << child->renderName() << " )" << ", " << + (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; +#endif + + if ( !child->isTableCell() ) { + // TR > FORM quirk (???) + if (child->element() && child->element()->isHTMLElement() && child->element()->id() == ID_FORM && + static_cast(child->element())->isMalformed()) + { + RenderContainer::addChild(child, beforeChild); + return; + } + + RenderObject* last = beforeChild; + if (!last) + last = lastChild(); + if (last && last->isAnonymous() && last->isTableCell()) { + last->addChild(child); + return; + } + + // If beforeChild is inside an anonymous cell, insert into the cell. + if (last && !last->isTableCell() && last->parent() && last->parent()->isAnonymous()) { + last->parent()->addChild(child, beforeChild); + return; + } + + RenderTableCell* cell = new (renderArena()) RenderTableCell(document() /* anonymous object */); + RenderStyle* newStyle = new RenderStyle(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(TABLE_CELL); + cell->setStyle(newStyle); + addChild(cell, beforeChild); + cell->addChild(child); + return; + } + + RenderTableCell* cell = static_cast(child); + + section()->addCell( cell, this ); + + RenderContainer::addChild(cell,beforeChild); + + if ( beforeChild || nextSibling() ) + section()->setNeedCellRecalc(); +} + +void RenderTableRow::layout() +{ + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + + RenderObject *child = firstChild(); + const bool pagedMode = canvas()->pagedMode(); + while( child ) { + if ( child->isTableCell() ) { + RenderTableCell *cell = static_cast(child); + if (pagedMode) { + cell->setNeedsLayout(true); + int oldHeight = child->height(); + cell->layout(); + if (oldHeight > 0 && child->containsPageBreak() && child->height() != oldHeight) + section()->addSpaceAt(child->yPos()+1, child->height() - oldHeight); + } else + if ( child->needsLayout() ) { + if (markedForRepaint()) + cell->setMarkedForRepaint( true ); + cell->calcVerticalMargins(); + cell->layout(); + cell->setCellTopExtra(0); + cell->setCellBottomExtra(0); + if (child->containsPageBreak()) setContainsPageBreak(true); + } + } + child = child->nextSibling(); + } + setMarkedForRepaint(false); + setNeedsLayout(false); +} + +int RenderTableRow::offsetLeft() const +{ + RenderObject *child = firstChild(); + while (child && !child->isTableCell()) + child = child->nextSibling(); + + if (!child) + return 0; + + return child->offsetLeft(); +} + +int RenderTableRow::offsetTop() const +{ + RenderObject *child = firstChild(); + while (child && !child->isTableCell()) + child = child->nextSibling(); + + if (!child) + return 0; + + return child->offsetTop() - + static_cast(child)->cellTopExtra(); +} + +int RenderTableRow::offsetHeight() const +{ + RenderObject *child = firstChild(); + while (child && !child->isTableCell()) + child = child->nextSibling(); + + if (!child) + return 0; + + return child->offsetHeight() + + static_cast(child)->cellTopExtra() + + static_cast(child)->cellBottomExtra(); +} + +short RenderTableRow::offsetWidth() const +{ + RenderObject *fc = firstChild(); + RenderObject *lc = lastChild(); + while (fc && !fc->isTableCell()) + fc = fc->nextSibling(); + while (lc && !lc->isTableCell()) + lc = lc->previousSibling(); + if (!lc || !fc) + return 0; + + return lc->xPos()+lc->width()-fc->xPos(); +} + +void RenderTableRow::paintRow( PaintInfo& pI, int tx, int ty, int w, int h ) +{ + if (pI.phase == PaintActionOutline) + paintOutline(pI.p, tx, ty, w, h, style()); +} + +void RenderTableRow::paint(PaintInfo& i, int tx, int ty) +{ + KHTMLAssert(layer()); + if (!layer()) + return; + + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if (child->isTableCell()) { + // Paint the row background behind the cell. + if (i.phase == PaintActionElementBackground || i.phase == PaintActionChildBackground) { + RenderTableCell* cell = static_cast(child); + cell->paintBackgroundsBehindCell(i, tx, ty, this); + } + if (!child->layer()) + child->paint(i, tx, ty); + } + } +} + +// Hit Testing +bool RenderTableRow::nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction action, bool inside) +{ + // Table rows cannot ever be hit tested. Effectively they do not exist. + // Just forward to our children always. + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { + // FIXME: We have to skip over inline flows, since they can show up inside table rows + // at the moment (a demoted inline for example). If we ever implement a + // table-specific hit-test method (which we should do for performance reasons anyway), + // then we can remove this check. + if (!child->layer() && !child->isInlineFlow() && child->nodeAtPoint(info, x, y, tx, ty, action, inside)) { + return true; + } + } + + return false; +} + +// ------------------------------------------------------------------------- + +RenderTableCell::RenderTableCell(DOM::NodeImpl* _node) + : RenderBlock(_node) +{ + _col = -1; + _row = -1; + updateFromElement(); + setShouldPaintBackgroundOrBorder(true); + _topExtra = 0; + _bottomExtra = 0; + m_percentageHeight = -1; + m_hasFlexedAnonymous = false; + m_widthChanged = false; +} + +void RenderTableCell::detach() +{ + if (parent() && section()) + section()->setNeedCellRecalc(); + + RenderBlock::detach(); +} + +void RenderTableCell::updateFromElement() +{ + DOM::NodeImpl *node = element(); + if ( node && (node->id() == ID_TD || node->id() == ID_TH) ) { + DOM::HTMLTableCellElementImpl *tc = static_cast(node); + cSpan = tc->colSpan(); + rSpan = tc->rowSpan(); + } else { + cSpan = rSpan = 1; + } +} + +Length RenderTableCell::styleOrColWidth() +{ + Length w = style()->width(); + if (colSpan() > 1 || !w.isVariable()) + return w; + RenderTableCol* col = table()->colElement(_col); + if (col) + w = col->style()->width(); + return w; +} + +void RenderTableCell::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(TableCell)::calcMinMaxWidth() known=" << minMaxKnown() << endl; +#endif + + if (section()->needCellRecalc) + section()->recalcCells(); + + RenderBlock::calcMinMaxWidth(); + if (element() && style()->whiteSpace() == NORMAL) { + // See if nowrap was set. + Length w = styleOrColWidth(); + DOMString nowrap = static_cast(element())->getAttribute(ATTR_NOWRAP); + if (!nowrap.isNull() && w.isFixed() && + m_minWidth < w.value() ) + // Nowrap is set, but we didn't actually use it because of the + // fixed width set on the cell. Even so, it is a WinIE/Moz trait + // to make the minwidth of the cell into the fixed width. They do this + // even in strict mode, so do not make this a quirk. Affected the top + // of hiptop.com. + m_minWidth = w.value(); + } + + setMinMaxKnown(); +} + +void RenderTableCell::calcWidth() +{ +} + +void RenderTableCell::setWidth( int width ) +{ + if ( width != m_width ) { + m_width = width; + m_widthChanged = true; + } +} + +void RenderTableCell::layout() +{ + layoutBlock( m_widthChanged ); + m_widthChanged = false; +} + +void RenderTableCell::close() +{ + RenderBlock::close(); + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(RenderTableCell)::close() total height =" << m_height << endl; +#endif +} + +bool RenderTableCell::requiresLayer() const { + // table-cell display is never positioned (css 2.1-9.7), so the only time a layer is needed + // is when overflow != visible (or when there is opacity when we support it) + return /* style()->opacity() < 1.0f || */ hasOverflowClip() || isRelPositioned(); +} + +void RenderTableCell::repaintRectangle(int x, int y, int w, int h, Priority p, bool f) +{ + RenderBlock::repaintRectangle(x, y, w, h + _topExtra + _bottomExtra, p, f); +} + +bool RenderTableCell::absolutePosition(int &xPos, int &yPos, bool f) const +{ + bool result = RenderBlock::absolutePosition(xPos, yPos, f); + xPos -= parent()->xPos(); // Rows are in the same coordinate space, so don't add their offset in. + yPos -= parent()->yPos(); + return result; +} + +int RenderTableCell::pageTopAfter(int y) const +{ + return section()->pageTopAfter(y+m_y + _topExtra) - (m_y + _topExtra); +} + +short RenderTableCell::baselinePosition( bool ) const +{ + RenderObject* o = firstChild(); + int offset = paddingTop() + borderTop(); + if (!o) return offset + contentHeight(); + while (o->firstChild()) { + if (!o->isInline()) + offset += o->paddingTop() + o->borderTop(); + o = o->firstChild(); + } + + if (!o->isInline()) + return paddingTop() + borderTop() + contentHeight(); + + offset += o->baselinePosition( true ); + return offset; +} + + +void RenderTableCell::setStyle( RenderStyle *newStyle ) +{ + if (parent() && section() && style() && style()->height() != newStyle->height()) + section()->setNeedCellRecalc(); + + newStyle->setDisplay(TABLE_CELL); + RenderBlock::setStyle( newStyle ); + setShouldPaintBackgroundOrBorder(true); + + if (newStyle->whiteSpace() == KHTML_NOWRAP) { + // Figure out if we are really nowrapping or if we should just + // use normal instead. If the width of the cell is fixed, then + // we don't actually use NOWRAP. + if (newStyle->width().isFixed()) + newStyle->setWhiteSpace(NORMAL); + else + newStyle->setWhiteSpace(NOWRAP); + } +} + +bool RenderTableCell::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside) +{ + int tx = _tx + m_x; + int ty = _ty + m_y; + + // also include the top and bottom extra space + inside |= style()->visibility() != HIDDEN + && (_y >= ty) && (_y < ty + height() + _topExtra + _bottomExtra) + && (_x >= tx) && (_x < tx + width()); + + return RenderBlock::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction, inside); +} + +// The following rules apply for resolving conflicts and figuring out which border +// to use. +// (1) Borders with the 'border-style' of 'hidden' take precedence over all other conflicting +// borders. Any border with this value suppresses all borders at this location. +// (2) Borders with a style of 'none' have the lowest priority. Only if the border properties of all +// the elements meeting at this edge are 'none' will the border be omitted (but note that 'none' is +// the default value for the border style.) +// (3) If none of the styles are 'hidden' and at least one of them is not 'none', then narrow borders +// are discarded in favor of wider ones. If several have the same 'border-width' then styles are preferred +// in this order: 'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset', 'groove', and the lowest: 'inset'. +// (4) If border styles differ only in color, then a style set on a cell wins over one on a row, +// which wins over a row group, column, column group and, lastly, table. It is undefined which color +// is used when two elements of the same type disagree. +static CollapsedBorderValue compareBorders(const CollapsedBorderValue& border1, + const CollapsedBorderValue& border2) +{ + // Sanity check the values passed in. If either is null, return the other. + if (!border2.exists()) return border1; + if (!border1.exists()) return border2; + + // Rule #1 above. + if (border1.style() == BHIDDEN || border2.style() == BHIDDEN) + return CollapsedBorderValue(); // No border should exist at this location. + + // Rule #2 above. A style of 'none' has lowest priority and always loses to any other border. + if (border2.style() == BNONE) return border1; + if (border1.style() == BNONE) return border2; + + // The first part of rule #3 above. Wider borders win. + if (border1.width() != border2.width()) + return border1.width() > border2.width() ? border1 : border2; + + // The borders have equal width. Sort by border style. + if (border1.style() != border2.style()) + return border1.style() > border2.style() ? border1 : border2; + + // The border have the same width and style. Rely on precedence (cell over row over row group, etc.) + return border1.precedence >= border2.precedence ? border1 : border2; +} + +CollapsedBorderValue RenderTableCell::collapsedLeftBorder() const +{ + // For border left, we need to check, in order of precedence: + // (1) Our left border. + CollapsedBorderValue result(&style()->borderLeft(), BCELL); + + // (2) The previous cell's right border. + RenderTableCell* prevCell = table()->cellLeft(this); + if (prevCell) { + result = compareBorders(result, CollapsedBorderValue(&prevCell->style()->borderRight(), BCELL)); + if (!result.exists()) return result; + } + else if (col() == 0) { + // (3) Our row's left border. + result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderLeft(), BROW)); + if (!result.exists()) return result; + + // (4) Our row group's left border. + result = compareBorders(result, CollapsedBorderValue(§ion()->style()->borderLeft(), BROWGROUP)); + if (!result.exists()) return result; + } + + // (5) Our column's left border. + RenderTableCol* colElt = table()->colElement(col()); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderLeft(), BCOL)); + if (!result.exists()) return result; + } + + // (6) The previous column's right border. + if (col() > 0) { + colElt = table()->colElement(col()-1); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderRight(), BCOL)); + if (!result.exists()) return result; + } + } + + if (col() == 0) { + // (7) The table's left border. + result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderLeft(), BTABLE)); + if (!result.exists()) return result; + } + + return result; +} + +CollapsedBorderValue RenderTableCell::collapsedRightBorder() const +{ + RenderTable* tableElt = table(); + bool inLastColumn = false; + int effCol = tableElt->colToEffCol(col()+colSpan()-1); + if (effCol == tableElt->numEffCols()-1) + inLastColumn = true; + + // For border right, we need to check, in order of precedence: + // (1) Our right border. + CollapsedBorderValue result = CollapsedBorderValue(&style()->borderRight(), BCELL); + + // (2) The next cell's left border. + if (!inLastColumn) { + RenderTableCell* nextCell = tableElt->cellRight(this); + if (nextCell) { + result = compareBorders(result, CollapsedBorderValue(&nextCell->style()->borderLeft(), BCELL)); + if (!result.exists()) return result; + } + } + else { + // (3) Our row's right border. + result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderRight(), BROW)); + if (!result.exists()) return result; + + // (4) Our row group's right border. + result = compareBorders(result, CollapsedBorderValue(§ion()->style()->borderRight(), BROWGROUP)); + if (!result.exists()) return result; + } + + // (5) Our column's right border. + RenderTableCol* colElt = table()->colElement(col()+colSpan()-1); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderRight(), BCOL)); + if (!result.exists()) return result; + } + + // (6) The next column's left border. + if (!inLastColumn) { + colElt = tableElt->colElement(col()+colSpan()); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderLeft(), BCOL)); + if (!result.exists()) return result; + } + } + else { + // (7) The table's right border. + result = compareBorders(result, CollapsedBorderValue(&tableElt->style()->borderRight(), BTABLE)); + if (!result.exists()) return result; + } + + return result; +} + +CollapsedBorderValue RenderTableCell::collapsedTopBorder() const +{ + // For border top, we need to check, in order of precedence: + // (1) Our top border. + CollapsedBorderValue result = CollapsedBorderValue(&style()->borderTop(), BCELL); + + RenderTableCell* prevCell = table()->cellAbove(this); + if (prevCell) { + // (2) A previous cell's bottom border. + result = compareBorders(result, CollapsedBorderValue(&prevCell->style()->borderBottom(), BCELL)); + if (!result.exists()) return result; + } + + // (3) Our row's top border. + result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderTop(), BROW)); + if (!result.exists()) return result; + + // (4) The previous row's bottom border. + if (prevCell) { + RenderObject* prevRow = 0; + if (prevCell->section() == section()) + prevRow = parent()->previousSibling(); + else + prevRow = prevCell->section()->lastChild(); + + if (prevRow) { + result = compareBorders(result, CollapsedBorderValue(&prevRow->style()->borderBottom(), BROW)); + if (!result.exists()) return result; + } + } + + // Now check row groups. + RenderTableSection* currSection = section(); + if (row() == 0) { + // (5) Our row group's top border. + result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderTop(), BROWGROUP)); + if (!result.exists()) return result; + + // (6) Previous row group's bottom border. + currSection = table()->sectionAbove(currSection); + if (currSection) { + result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderBottom(), BROWGROUP)); + if (!result.exists()) + return result; + } + } + + if (!currSection) { + // (8) Our column's top border. + RenderTableCol* colElt = table()->colElement(col()); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderTop(), BCOL)); + if (!result.exists()) return result; + } + + // (9) The table's top border. + result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderTop(), BTABLE)); + if (!result.exists()) return result; + } + + return result; +} + +CollapsedBorderValue RenderTableCell::collapsedBottomBorder() const +{ + // For border top, we need to check, in order of precedence: + // (1) Our bottom border. + CollapsedBorderValue result = CollapsedBorderValue(&style()->borderBottom(), BCELL); + + RenderTableCell* nextCell = table()->cellBelow(this); + if (nextCell) { + // (2) A following cell's top border. + result = compareBorders(result, CollapsedBorderValue(&nextCell->style()->borderTop(), BCELL)); + if (!result.exists()) return result; + } + + // (3) Our row's bottom border. (FIXME: Deal with rowspan!) + result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderBottom(), BROW)); + if (!result.exists()) return result; + + // (4) The next row's top border. + if (nextCell) { + result = compareBorders(result, CollapsedBorderValue(&nextCell->parent()->style()->borderTop(), BROW)); + if (!result.exists()) return result; + } + + // Now check row groups. + RenderTableSection* currSection = section(); + if (row()+rowSpan() >= static_cast(currSection)->numRows()) { + // (5) Our row group's bottom border. + result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderBottom(), BROWGROUP)); + if (!result.exists()) return result; + + // (6) Following row group's top border. + currSection = table()->sectionBelow(currSection); + if (currSection) { + result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderTop(), BROWGROUP)); + if (!result.exists()) + return result; + } + } + + if (!currSection) { + // (8) Our column's bottom border. + RenderTableCol* colElt = table()->colElement(col()); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderBottom(), BCOL)); + if (!result.exists()) return result; + } + + // (9) The table's bottom border. + result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderBottom(), BTABLE)); + if (!result.exists()) return result; + } + + return result; +} + +int RenderTableCell::borderLeft() const +{ + if (table()->collapseBorders()) { + CollapsedBorderValue border = collapsedLeftBorder(); + if (border.exists()) + return (border.width()+1)/2; // Give the extra pixel to top and left. + return 0; + } + return RenderBlock::borderLeft(); +} + +int RenderTableCell::borderRight() const +{ + if (table()->collapseBorders()) { + CollapsedBorderValue border = collapsedRightBorder(); + if (border.exists()) + return border.width()/2; + return 0; + } + return RenderBlock::borderRight(); +} + +int RenderTableCell::borderTop() const +{ + if (table()->collapseBorders()) { + CollapsedBorderValue border = collapsedTopBorder(); + if (border.exists()) + return (border.width()+1)/2; // Give the extra pixel to top and left. + return 0; + } + return RenderBlock::borderTop(); +} + +int RenderTableCell::borderBottom() const +{ + if (table()->collapseBorders()) { + CollapsedBorderValue border = collapsedBottomBorder(); + if (border.exists()) + return border.width()/2; + return 0; + } + return RenderBlock::borderBottom(); +} + +#ifdef BOX_DEBUG +#include + +static void outlineBox(TQPainter *p, int _tx, int _ty, int w, int h) +{ + p->setPen(TQPen(TQColor("yellow"), 3, Qt::DotLine)); + p->setBrush( Qt::NoBrush ); + p->drawRect(_tx, _ty, w, h ); +} +#endif + +void RenderTableCell::paint(PaintInfo& pI, int _tx, int _ty) +{ + +#ifdef TABLE_PRINT + kdDebug( 6040 ) << renderName() << "(RenderTableCell)::paint() w/h = (" << width() << "/" << height() << ")" << " _y/_h=" << pI.r.y() << "/" << pI.r.height() << endl; +#endif + + if (needsLayout()) return; + + _tx += m_x; + _ty += m_y/* + _topExtra*/; + + RenderTable *tbl = table(); + + // check if we need to do anything at all... + int os = kMax(tbl->currentBorderStyle() ? (tbl->currentBorderStyle()->border->width+1)/2 : 0, 2*maximalOutlineSize(pI.phase)); + if (!overhangingContents() && ((_ty >= pI.r.y() + pI.r.height() + os) + || (_ty + _topExtra + m_height + _bottomExtra <= pI.r.y() - os))) return; + + if (pI.phase == PaintActionOutline) { + paintOutline( pI.p, _tx, _ty, width(), height() + borderTopExtra() + borderBottomExtra(), style()); + } + + if (pI.phase == PaintActionCollapsedTableBorders && style()->visibility() == VISIBLE) { + int w = width(); + int h = height() + borderTopExtra() + borderBottomExtra(); + paintCollapsedBorder(pI.p, _tx, _ty, w, h); + } + else + RenderBlock::paintObject(pI, _tx, _ty + _topExtra, false); + +#ifdef BOX_DEBUG + ::outlineBox( p, _tx, _ty - _topExtra, width(), height() + borderTopExtra() + borderBottomExtra()); +#endif +} + +static EBorderStyle collapsedBorderStyle(EBorderStyle style) +{ + if (style == OUTSET) + style = GROOVE; + else if (style == INSET) + style = RIDGE; + return style; +} + +struct CollapsedBorder { + CollapsedBorder(){} + + CollapsedBorderValue border; + RenderObject::BorderSide side; + bool shouldPaint; + int x1; + int y1; + int x2; + int y2; + EBorderStyle style; +}; + +class CollapsedBorders +{ +public: + CollapsedBorders() :count(0) {} + + void addBorder(const CollapsedBorderValue& b, RenderObject::BorderSide s, bool paint, + int _x1, int _y1, int _x2, int _y2, + EBorderStyle _style) + { + if (b.exists() && paint) { + borders[count].border = b; + borders[count].side = s; + borders[count].shouldPaint = paint; + borders[count].x1 = _x1; + borders[count].x2 = _x2; + borders[count].y1 = _y1; + borders[count].y2 = _y2; + borders[count].style = _style; + count++; + } + } + + CollapsedBorder* nextBorder() { + for (int i = 0; i < count; i++) { + if (borders[i].border.exists() && borders[i].shouldPaint) { + borders[i].shouldPaint = false; + return &borders[i]; + } + } + + return 0; + } + + CollapsedBorder borders[4]; + int count; +}; + +static void addBorderStyle(TQValueList& borderStyles, CollapsedBorderValue borderValue) +{ + if (!borderValue.exists() || borderStyles.contains(borderValue)) + return; + + TQValueListIterator it = borderStyles.begin(); + TQValueListIterator end = borderStyles.end(); + for (; it != end; ++it) { + CollapsedBorderValue result = compareBorders(*it, borderValue); + if (result == *it) { + borderStyles.insert(it, borderValue); + return; + } + } + + borderStyles.append(borderValue); +} + +void RenderTableCell::collectBorders(TQValueList& borderStyles) +{ + addBorderStyle(borderStyles, collapsedLeftBorder()); + addBorderStyle(borderStyles, collapsedRightBorder()); + addBorderStyle(borderStyles, collapsedTopBorder()); + addBorderStyle(borderStyles, collapsedBottomBorder()); +} + +void RenderTableCell::paintCollapsedBorder(TQPainter* p, int _tx, int _ty, int w, int h) +{ + if (!table()->currentBorderStyle()) + return; + + CollapsedBorderValue leftVal = collapsedLeftBorder(); + CollapsedBorderValue rightVal = collapsedRightBorder(); + CollapsedBorderValue topVal = collapsedTopBorder(); + CollapsedBorderValue bottomVal = collapsedBottomBorder(); + + // Adjust our x/y/width/height so that we paint the collapsed borders at the correct location. + int topWidth = topVal.width(); + int bottomWidth = bottomVal.width(); + int leftWidth = leftVal.width(); + int rightWidth = rightVal.width(); + + _tx -= leftWidth/2; + _ty -= topWidth/2; + w += leftWidth/2 + (rightWidth+1)/2; + h += topWidth/2 + (bottomWidth+1)/2; + + bool tt = topVal.isTransparent(); + bool bt = bottomVal.isTransparent(); + bool rt = rightVal.isTransparent(); + bool lt = leftVal.isTransparent(); + + EBorderStyle ts = collapsedBorderStyle(topVal.style()); + EBorderStyle bs = collapsedBorderStyle(bottomVal.style()); + EBorderStyle ls = collapsedBorderStyle(leftVal.style()); + EBorderStyle rs = collapsedBorderStyle(rightVal.style()); + + bool render_t = ts > BHIDDEN && !tt && (topVal.precedence != BCELL || *topVal.border == style()->borderTop()); + bool render_l = ls > BHIDDEN && !lt && (leftVal.precedence != BCELL || *leftVal.border == style()->borderLeft()); + bool render_r = rs > BHIDDEN && !rt && (rightVal.precedence != BCELL || *rightVal.border == style()->borderRight()); + bool render_b = bs > BHIDDEN && !bt && (bottomVal.precedence != BCELL || *bottomVal.border == style()->borderBottom()); + + // We never paint diagonals at the joins. We simply let the border with the highest + // precedence paint on top of borders with lower precedence. + CollapsedBorders borders; + borders.addBorder(topVal, BSTop, render_t, _tx, _ty, _tx + w, _ty + topWidth, ts); + borders.addBorder(bottomVal, BSBottom, render_b, _tx, _ty + h - bottomWidth, _tx + w, _ty + h, bs); + borders.addBorder(leftVal, BSLeft, render_l, _tx, _ty, _tx + leftWidth, _ty + h, ls); + borders.addBorder(rightVal, BSRight, render_r, _tx + w - rightWidth, _ty, _tx + w, _ty + h, rs); + + for (CollapsedBorder* border = borders.nextBorder(); border; border = borders.nextBorder()) { + if (border->border == *table()->currentBorderStyle()) + drawBorder(p, border->x1, border->y1, border->x2, border->y2, border->side, + border->border.color(), style()->color(), border->style, 0, 0); + } +} + +void RenderTableCell::paintBackgroundsBehindCell(PaintInfo& pI, int _tx, int _ty, RenderObject* backgroundObject) +{ + if (!backgroundObject) + return; + + RenderTable* tableElt = table(); + if (backgroundObject != this) { + _tx += m_x; + _ty += m_y + _topExtra; + } + + int w = width(); + int h = height() + borderTopExtra() + borderBottomExtra(); + _ty -= borderTopExtra(); + + int my = kMax(_ty,pI.r.y()); + int end = kMin( pI.r.y() + pI.r.height(), _ty + h ); + int mh = end - my; + + TQColor c = backgroundObject->style()->backgroundColor(); + const BackgroundLayer* bgLayer = backgroundObject->style()->backgroundLayers(); + + if (bgLayer->hasImage() || c.isValid()) { + // We have to clip here because the background would paint + // on top of the borders otherwise. This only matters for cells and rows. + bool hasLayer = backgroundObject->layer() && (backgroundObject == this || backgroundObject == parent()); + if (hasLayer && tableElt->collapseBorders()) { + pI.p->save(); + TQRect clipRect(_tx + borderLeft(), _ty + borderTop(), w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); + clipRect = pI.p->xForm(clipRect); + TQRegion creg(clipRect); + TQRegion old = pI.p->clipRegion(); + if (!old.isNull()) + creg = old.intersect(creg); + pI.p->setClipRegion(creg); + } + paintBackground(pI.p, c, bgLayer, my, mh, _tx, _ty, w, h); + if (hasLayer && tableElt->collapseBorders()) + pI.p->restore(); + } +} + +void RenderTableCell::paintBoxDecorations(PaintInfo& pI, int _tx, int _ty) +{ + RenderTable* tableElt = table(); + bool drawBorders = true; + // Moz paints bgcolor/bgimage on s in quirks mode even if + // empty-cells are on. Fixes regression on #43426, attachment #354 + if (!tableElt->collapseBorders() && style()->emptyCells() == HIDE && !firstChild()) + drawBorders = false; + if (!style()->htmlHacks() && !drawBorders) return; + + // Paint our cell background. + paintBackgroundsBehindCell(pI, _tx, _ty, this); + + int w = width(); + int h = height() + borderTopExtra() + borderBottomExtra(); + _ty -= borderTopExtra(); + + if (drawBorders && style()->hasBorder() && !tableElt->collapseBorders()) + paintBorder(pI.p, _tx, _ty, w, h, style()); +} + + +#ifdef ENABLE_DUMP +void RenderTableCell::dump(TQTextStream &stream, const TQString &ind) const +{ + RenderFlow::dump(stream,ind); + stream << " row=" << _row; + stream << " col=" << _col; + stream << " rSpan=" << rSpan; + stream << " cSpan=" << cSpan; +// *stream << " nWrap=" << nWrap; +} +#endif + +// ------------------------------------------------------------------------- + +RenderTableCol::RenderTableCol(DOM::NodeImpl* node) + : RenderContainer(node), m_span(1) +{ + // init RenderObject attributes + setInline(true); // our object is not Inline + updateFromElement(); +} + +void RenderTableCol::updateFromElement() +{ + DOM::NodeImpl *node = element(); + if ( node && (node->id() == ID_COL || node->id() == ID_COLGROUP) ) { + DOM::HTMLTableColElementImpl *tc = static_cast(node); + m_span = tc->span(); + } else + m_span = ! ( style() && style()->display() == TABLE_COLUMN_GROUP ); +} + +#ifdef ENABLE_DUMP +void RenderTableCol::dump(TQTextStream &stream, const TQString &ind) const +{ + RenderContainer::dump(stream,ind); + stream << " _span=" << m_span; +} +#endif + +// ------------------------------------------------------------------------- + +TableSectionIterator::TableSectionIterator(RenderTable *table, bool fromEnd) +{ + if (fromEnd) { + sec = table->foot; + if (sec) return; + + sec = static_cast(table->lastChild()); + while (sec && (!sec->isTableSection() + || sec == table->head || sec == table->foot)) + sec = static_cast(sec->previousSibling()); + if (sec) return; + + sec = table->head; + } else { + sec = table->head; + if (sec) return; + + sec = static_cast(table->firstChild()); + while (sec && (!sec->isTableSection() + || sec == table->head || sec == table->foot)) + sec = static_cast(sec->nextSibling()); + if (sec) return; + + sec = table->foot; + }/*end if*/ + +} + +TableSectionIterator &TableSectionIterator::operator ++() +{ + RenderTable *table = sec->table(); + if (sec == table->head) { + + sec = static_cast(table->firstChild()); + while (sec && (!sec->isTableSection() + || sec == table->head || sec == table->foot)) + sec = static_cast(sec->nextSibling()); + if (sec) return *this; + + } else if (sec == table->foot) { + sec = 0; + return *this; + + } else { + + do { + sec = static_cast(sec->nextSibling()); + } while (sec && (!sec->isTableSection() || sec == table->head || sec == table->foot)); + if (sec) return *this; + + }/*end if*/ + + sec = table->foot; + return *this; +} + +TableSectionIterator &TableSectionIterator::operator --() +{ + RenderTable *table = sec->table(); + if (sec == table->foot) { + + sec = static_cast(table->lastChild()); + while (sec && (!sec->isTableSection() + || sec == table->head || sec == table->foot)) + sec = static_cast(sec->previousSibling()); + if (sec) return *this; + + } else if (sec == table->head) { + sec = 0; + return *this; + + } else { + + do { + sec = static_cast(sec->previousSibling()); + } while (sec && (!sec->isTableSection() || sec == table->head || sec == table->foot)); + if (sec) return *this; + + }/*end if*/ + + sec = table->foot; + return *this; +} + +#undef TABLE_DEBUG +#undef DEBUG_LAYOUT +#undef BOX_DEBUG -- cgit v1.2.3