diff options
Diffstat (limited to 'tdehtml/rendering/table_layout.cpp')
-rw-r--r-- | tdehtml/rendering/table_layout.cpp | 1193 |
1 files changed, 1193 insertions, 0 deletions
diff --git a/tdehtml/rendering/table_layout.cpp b/tdehtml/rendering/table_layout.cpp new file mode 100644 index 000000000..eb8153331 --- /dev/null +++ b/tdehtml/rendering/table_layout.cpp @@ -0,0 +1,1193 @@ +/* + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 2002 Lars Knoll (knoll@kde.org) + * (C) 2002 Dirk Mueller (mueller@kde.org) + * + * 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ +#include "table_layout.h" +#include "render_table.h" + +#include <kglobal.h> + +using namespace tdehtml; + +// #define DEBUG_LAYOUT + +/* + The text below is from the CSS 2.1 specs. + + Fixed table layout + ------------------ + + With this (fast) algorithm, the horizontal layout of the table does + not depend on the contents of the cells; it only depends on the + table's width, the width of the columns, and borders or cell + spacing. + + The table's width may be specified explicitly with the 'width' + property. A value of 'auto' (for both 'display: table' and 'display: + inline-table') means use the automatic table layout algorithm. + + In the fixed table layout algorithm, the width of each column is + determined as follows: + + 1. A column element with a value other than 'auto' for the 'width' + property sets the width for that column. + + 2. Otherwise, a cell in the first row with a value other than + 'auto' for the 'width' property sets the width for that column. If + the cell spans more than one column, the width is divided over the + columns. + + 3. Any remaining columns equally divide the remaining horizontal + table space (minus borders or cell spacing). + + The width of the table is then the greater of the value of the + 'width' property for the table element and the sum of the column + widths (plus cell spacing or borders). If the table is wider than + the columns, the extra space should be distributed over the columns. + + + In this manner, the user agent can begin to lay out the table once + the entire first row has been received. Cells in subsequent rows do + not affect column widths. Any cell that has content that overflows + uses the 'overflow' property to determine whether to clip the + overflow content. + +_____________________________________________________ + + This is not quite true when comparing to IE. IE always honors + table-layout:fixed and treats a variable table width as 100%. Makes + a lot of sense, and is implemented here the same way. + +*/ + +FixedTableLayout::FixedTableLayout( RenderTable *table ) + : TableLayout ( table ) +{ +} + +FixedTableLayout::~FixedTableLayout() +{ +} + +int FixedTableLayout::calcWidthArray() +{ + int usedWidth = 0; + + // iterate over all <col> elements + RenderObject *child = table->firstChild(); + int cCol = 0; + int nEffCols = table->numEffCols(); + width.resize( nEffCols ); + width.fill( Length( Variable ) ); + +#ifdef DEBUG_LAYOUT + tqDebug("FixedTableLayout::calcWidthArray()" ); + tqDebug(" col elements:"); +#endif + + Length grpWidth; + while ( child ) { + if ( child->isTableCol() ) { + RenderTableCol *col = static_cast<RenderTableCol *>(child); + int span = col->span(); + if ( col->firstChild() ) { + grpWidth = col->style()->width(); + } else { + Length w = col->style()->width(); + if ( w.isVariable() ) + w = grpWidth; + int effWidth = 0; + if ( w.isFixed() && w.value() > 0 ) { + effWidth = w.value(); + effWidth = KMIN( effWidth, 32760 ); + } +#ifdef DEBUG_LAYOUT + tqDebug(" col element: effCol=%d, span=%d: %d w=%d type=%d", + cCol, span, effWidth, w.value(), w.type()); +#endif + int usedSpan = 0; + int i = 0; + while ( usedSpan < span ) { + if( cCol + i >= nEffCols ) { + table->appendColumn( span - usedSpan ); + nEffCols++; + width.resize( nEffCols ); + width[nEffCols-1] = Length(); + } + int eSpan = table->spanOfEffCol( cCol+i ); + if ( (w.isFixed() || w.isPercent()) && w.value() > 0 ) { + width[cCol+i] = Length( w.value() * eSpan, w.type() ); + usedWidth += effWidth * eSpan; +#ifdef DEBUG_LAYOUT + tqDebug(" setting effCol %d (span=%d) to width %d(type=%d)", + cCol+i, eSpan, width[cCol+i].value(), width[cCol+i].type() ); +#endif + } + usedSpan += eSpan; + i++; + } + cCol += i; + } + } else { + break; + } + + RenderObject *next = child->firstChild(); + if ( !next ) + next = child->nextSibling(); + if ( !next && child->parent()->isTableCol() ) { + next = child->parent()->nextSibling(); + grpWidth = Length(); + } + child = next; + } + +#ifdef DEBUG_LAYOUT + tqDebug(" first row:"); +#endif + // iterate over the first row in case some are unspecified. + RenderTableSection *section = table->head; + if ( !section ) + section = table->firstBody; + if ( !section ) + section = table->foot; + if ( section && section->firstChild() ) { + cCol = 0; + // get the first cell in the first row + child = section->firstChild()->firstChild(); + while ( child ) { + if ( child->isTableCell() ) { + RenderTableCell *cell = static_cast<RenderTableCell *>(child); + Length w = cell->styleOrColWidth(); + int span = cell->colSpan(); + int effWidth = 0; + if ( (w.isFixed() || w.isPercent()) && w.value() > 0 ) { + effWidth = w.value(); + effWidth = kMin( effWidth, 32760 ); + } +#ifdef DEBUG_LAYOUT + tqDebug(" table cell: effCol=%d, span=%d: %d", cCol, span, effWidth); +#endif + int usedSpan = 0; + int i = 0; + while ( usedSpan < span ) { + Q_ASSERT( cCol + i < nEffCols ); + int eSpan = table->spanOfEffCol( cCol+i ); + // only set if no col element has already set it. + if ( width[cCol+i].isVariable() && !w.isVariable() ) { + width[cCol+i] = Length( w.value()*eSpan, w.type() ); + usedWidth += effWidth*eSpan; +#ifdef DEBUG_LAYOUT + tqDebug(" setting effCol %d (span=%d) to width %d(type=%d)", + cCol+i, eSpan, width[cCol+i].value(), width[cCol+i].type() ); +#endif + } +#ifdef DEBUG_LAYOUT + else { + tqDebug(" width of col %d already defined (span=%d)", cCol, table->spanOfEffCol( cCol ) ); + } +#endif + usedSpan += eSpan; + i++; + } + cCol += i; + } else { + Q_ASSERT( false ); + } + child = child->nextSibling(); + } + } + + return usedWidth; + +} + +void FixedTableLayout::calcMinMaxWidth() +{ + // we might want to wait until we have all of the first row before + // layouting for the first time. + + // only need to calculate the minimum width as the sum of the + // cols/cells with a fixed width. + // + // The maximum width is kMax( minWidth, tableWidth ) if table + // width is fixed. If table width is percent, we set maxWidth to + // unlimited. + + int bs = table->bordersPaddingAndSpacing(); + int tableWidth = 0; + if (table->style()->width().isFixed()) { + tableWidth = table->calcBoxWidth(table->style()->width().value()); + } + + int mw = calcWidthArray() + bs; + table->m_minWidth = kMin( kMax( mw, tableWidth ), 0x7fff ); + table->m_maxWidth = table->m_minWidth; + + if ( !tableWidth ) { + bool haveNonFixed = false; + for ( unsigned int i = 0; i < width.size(); i++ ) { + if ( !width[i].isFixed() ) { + haveNonFixed = true; + break; + } + } + if ( haveNonFixed ) + table->m_maxWidth = 0x7fff; + } +#ifdef DEBUG_LAYOUT + tqDebug("FixedTableLayout::calcMinMaxWidth: minWidth=%d, maxWidth=%d", table->m_minWidth, table->m_maxWidth ); +#endif +} + +void FixedTableLayout::layout() +{ + int tableWidth = table->width() - table->bordersPaddingAndSpacing(); + int available = tableWidth; + int nEffCols = table->numEffCols(); +#ifdef DEBUG_LAYOUT + tqDebug("FixedTableLayout::layout: tableWidth=%d, numEffCols=%d", tableWidth, nEffCols); +#endif + + + TQMemArray<int> calcWidth; + calcWidth.resize( nEffCols ); + calcWidth.fill( -1 ); + + // first assign fixed width + for ( int i = 0; i < nEffCols; i++ ) { + if ( width[i].isFixed() ) { + calcWidth[i] = width[i].value(); + available -= width[i].value(); + } + } + + // assign percent width + if ( available > 0 ) { + int totalPercent = 0; + for ( int i = 0; i < nEffCols; i++ ) + if ( width[i].isPercent() ) + totalPercent += width[i].value(); + + // calculate how much to distribute to percent cells. + int base = tableWidth * totalPercent / 100; + if ( base > available ) + base = available; + +#ifdef DEBUG_LAYOUT + tqDebug("FixedTableLayout::layout: assigning percent width, base=%d, totalPercent=%d", base, totalPercent); +#endif + for ( int i = 0; available > 0 && i < nEffCols; i++ ) { + if ( width[i].isPercent() ) { + // totalPercent may be 0 below if all %-width specifed are 0%. (#172557) + int w = totalPercent ? base * width[i].value() / totalPercent : 0; + available -= w; + calcWidth[i] = w; + } + } + } + + // assign variable width + if ( available > 0 ) { + int totalVariable = 0; + for ( int i = 0; i < nEffCols; i++ ) + if ( width[i].isVariable() ) + totalVariable++; + + for ( int i = 0; available > 0 && i < nEffCols; i++ ) { + if ( width[i].isVariable() ) { + // totalVariable may be 0 below if all the variable widths specified are 0. + int w = totalVariable ? available / totalVariable : 0; + available -= w; + calcWidth[i] = w; + totalVariable--; + } + } + } + + for ( int i = 0; i < nEffCols; i++ ) + if ( calcWidth[i] < 0 ) + calcWidth[i] = 0; // IE gives min 1 px... + + // spread extra space over columns + if ( available > 0 ) { + int total = nEffCols; + // still have some width to spread + int i = nEffCols; + while ( i-- ) { + int w = available / total; + available -= w; + total--; + calcWidth[i] += w; + } + } + + int pos = 0; + int hspacing = table->borderHSpacing(); + for ( int i = 0; i < nEffCols; i++ ) { +#ifdef DEBUG_LAYOUT + tqDebug("col %d: %d (width %d)", i, pos, calcWidth[i] ); +#endif + table->columnPos[i] = pos; + pos += calcWidth[i] + hspacing; + } + table->columnPos[table->columnPos.size()-1] = pos; +} + +// ------------------------------------------------------------------------- +// ------------------------------------------------------------------------- + + +AutoTableLayout::AutoTableLayout( RenderTable* table ) + : TableLayout( table ) +{ + percentagesDirty = true; + effWidthDirty = true; + total_percent = 0; + hasPercent = false; +} + +AutoTableLayout::~AutoTableLayout() +{ +} + +/* recalculates the full structure needed to do layouting and minmax calculations. + This is usually calculated on the fly, but needs to be done fully when table cells change + dynamically +*/ +void AutoTableLayout::recalcColumn( int effCol ) +{ + Layout &l = layoutStruct[effCol]; + + RenderObject *child = table->firstChild(); + // first we iterate over all rows. + + RenderTableCell *fixedContributor = 0; + RenderTableCell *maxContributor = 0; + + while ( child ) { + if ( child->isTableSection() ) { + RenderTableSection *section = static_cast<RenderTableSection *>(child); + int numRows = section->numRows(); + RenderTableCell *last = 0; + for ( int i = 0; i < numRows; i++ ) { + RenderTableCell *cell = section->cellAt( i, effCol ); + if ( cell == (RenderTableCell *)-1 ) + continue; + if ( cell && cell->colSpan() == 1 ) { + // A cell originates in this column. Ensure we have + // a min/max width of at least 1px for this column now. + l.minWidth = kMax(int( l.minWidth ), 1); + l.maxWidth = kMax(int( l.maxWidth ), 1); + + if ( !cell->minMaxKnown() ) + cell->calcMinMaxWidth(); + if ( cell->minWidth() > l.minWidth ) + l.minWidth = cell->minWidth(); + if ( cell->maxWidth() > l.maxWidth ) { + l.maxWidth = cell->maxWidth(); + maxContributor = cell; + } + + Length w = cell->styleOrColWidth(); + w.l.value = kMin( 32767, kMax( 0, w.value() ) ); + switch( w.type() ) { + case Fixed: + // ignore width=0 + if ( w.value() > 0 && !l.width.isPercent() ) { + int wval = cell->calcBoxWidth(w.value()); + if ( l.width.isFixed() ) { + // Nav/IE weirdness + if ((wval > l.width.value()) || + ((l.width.value() == wval) && (maxContributor == cell))) { + l.width.l.value = wval; + fixedContributor = cell; + } + } else { + l.width = Length( wval, Fixed ); + fixedContributor = cell; + } + } + break; + case Percent: + hasPercent = true; + if ( w.value() > 0 && (!l.width.isPercent() || w.value() > l.width.value() ) ) + l.width = w; + break; + case Relative: + if ( w.isVariable() || (w.isRelative() && w.value() > l.width.value() ) ) + l.width = w; + default: + break; + } + } else { + if ( cell && (!effCol || section->cellAt( i, effCol-1 ) != cell) ) { + // This spanning cell originates in this column. Ensure we have + // a min/max width of at least 1px for this column now. + l.minWidth = kMax(int( l.minWidth ), 1); + l.maxWidth = kMax(int( l.maxWidth ), 1); + insertSpanCell( cell ); + } + last = cell; + } + } + } + child = child->nextSibling(); + } + + // Nav/IE weirdness + if ( l.width.isFixed() ) { + if ( table->style()->htmlHacks() + && (l.maxWidth > l.width.value()) && (fixedContributor != maxContributor)) { + l.width = Length(); + fixedContributor = 0; + } + } + + l.maxWidth = kMax(l.maxWidth, int(l.minWidth)); +#ifdef DEBUG_LAYOUT + tqDebug("col %d, final min=%d, max=%d, width=%d(%d)", effCol, l.minWidth, l.maxWidth, l.width.value(), l.width.type() ); +#endif + + // ### we need to add col elements aswell +} + + +void AutoTableLayout::fullRecalc() +{ + percentagesDirty = true; + hasPercent = false; + effWidthDirty = true; + + int nEffCols = table->numEffCols(); + layoutStruct.resize( nEffCols ); + layoutStruct.fill( Layout() ); + spanCells.fill( 0 ); + + RenderObject *child = table->firstChild(); + Length grpWidth; + int cCol = 0; + while ( child ) { + if ( child->isTableCol() ) { + RenderTableCol *col = static_cast<RenderTableCol *>(child); + int span = col->span(); + if ( col->firstChild() ) { + grpWidth = col->style()->width(); + } else { + Length w = col->style()->width(); + if ( w.isVariable() ) + w = grpWidth; + if ( (w.isFixed() && w.value() == 0) || + (w.isPercent() && w.value() == 0) ) + w = Length(); + int cEffCol = table->colToEffCol( cCol ); +#ifdef DEBUG_LAYOUT + tqDebug(" col element %d (eff=%d): Length=%d(%d), span=%d, effColSpan=%d", cCol, cEffCol, w.value(), w.type(), span, table->spanOfEffCol(cEffCol ) ); +#endif + if ( !w.isVariable() && span == 1 && cEffCol < nEffCols ) { + if ( table->spanOfEffCol( cEffCol ) == 1 ) { + layoutStruct[cEffCol].width = w; + if (w.isFixed() && layoutStruct[cEffCol].maxWidth < w.value()) + layoutStruct[cEffCol].maxWidth = w.value(); + } + } + cCol += span; + } + } else { + break; + } + + RenderObject *next = child->firstChild(); + if ( !next ) + next = child->nextSibling(); + if ( !next && child->parent()->isTableCol() ) { + next = child->parent()->nextSibling(); + grpWidth = Length(); + } + child = next; + } + + + for ( int i = 0; i < nEffCols; i++ ) + recalcColumn( i ); +} + +static bool shouldScaleColumns(RenderTable* table) +{ + // A special case. If this table is not fixed width and contained inside + // a cell, then don't bloat the maxwidth by examining percentage growth. + bool scale = true; + while (table) { + Length tw = table->style()->width(); + if ((tw.isVariable() || tw.isPercent()) && !table->isPositioned()) { + RenderBlock* cb = table->containingBlock(); + while (cb && !cb->isCanvas() && !cb->isTableCell() && + cb->style()->width().isVariable() && !cb->isPositioned()) + cb = cb->containingBlock(); + + table = 0; + if (cb && cb->isTableCell() && + (cb->style()->width().isVariable() || cb->style()->width().isPercent())) { + if (tw.isPercent()) + scale = false; + else { + RenderTableCell* cell = static_cast<RenderTableCell*>(cb); + if (cell->colSpan() > 1 || cell->table()->style()->width().isVariable()) + scale = false; + else + table = cell->table(); + } + } + } + else + table = 0; + } + return scale; +} + +void AutoTableLayout::calcMinMaxWidth() +{ +#ifdef DEBUG_LAYOUT + tqDebug("AutoTableLayout::calcMinMaxWidth"); +#endif + fullRecalc(); + + int spanMaxWidth = calcEffectiveWidth(); + int minWidth = 0; + int maxWidth = 0; + int maxPercent = 0; + int maxNonPercent = 0; + + int remainingPercent = 100; + for ( unsigned int i = 0; i < layoutStruct.size(); i++ ) { + minWidth += layoutStruct[i].effMinWidth; + maxWidth += layoutStruct[i].effMaxWidth; + if ( layoutStruct[i].effWidth.isPercent() ) { + int percent = kMin(layoutStruct[i].effWidth.value(), remainingPercent); + int pw = ( layoutStruct[i].effMaxWidth * 100) / kMax(percent, 1); + remainingPercent -= percent; + maxPercent = kMax( pw, maxPercent ); + } else { + maxNonPercent += layoutStruct[i].effMaxWidth; + } + } + + if (shouldScaleColumns(table)) { + maxNonPercent = (maxNonPercent * 100 + 50) / kMax(remainingPercent, 1); + maxWidth = kMax( maxNonPercent, maxWidth ); + maxWidth = kMax( maxWidth, maxPercent ); + } + + maxWidth = kMax( maxWidth, spanMaxWidth ); + + int bs = table->bordersPaddingAndSpacing(); + minWidth += bs; + maxWidth += bs; + + Length tw = table->style()->width(); + if ( tw.isFixed() && tw.value() > 0 ) { + int width = table->calcBoxWidth(tw.value()); + minWidth = kMax( minWidth, width ); + maxWidth = minWidth; + } + + table->m_maxWidth = kMin(maxWidth, 0x7fff); + table->m_minWidth = kMin(minWidth, 0x7fff); +#ifdef DEBUG_LAYOUT + tqDebug(" minWidth=%d, maxWidth=%d", table->m_minWidth, table->m_maxWidth ); +#endif +} + +/* + This method takes care of colspans. + effWidth is the same as width for cells without colspans. If we have colspans, they get modified. + */ +int AutoTableLayout::calcEffectiveWidth() +{ + int tMaxWidth = 0; + + unsigned int nEffCols = layoutStruct.size(); + int hspacing = table->borderHSpacing(); +#ifdef DEBUG_LAYOUT + tqDebug("AutoTableLayout::calcEffectiveWidth for %d cols", nEffCols ); +#endif + for ( unsigned int i = 0; i < nEffCols; i++ ) { + layoutStruct[i].effWidth = layoutStruct[i].width; + layoutStruct[i].effMinWidth = layoutStruct[i].minWidth; + layoutStruct[i].effMaxWidth = layoutStruct[i].maxWidth; + } + + for ( unsigned int i = 0; i < spanCells.size(); i++ ) { + RenderTableCell *cell = spanCells[i]; + if ( !cell || cell == (RenderTableCell *)-1 ) + break; + int span = cell->colSpan(); + + Length w = cell->styleOrColWidth(); + if ( !w.isRelative() && w.value() == 0 ) + w = Length(); // make it Variable + + int col = table->colToEffCol( cell->col() ); + unsigned int lastCol = col; + int cMinWidth = cell->minWidth() + hspacing; + int cMaxWidth = cell->maxWidth() + hspacing; + int totalPercent = 0; + int minWidth = 0; + int maxWidth = 0; + bool allColsArePercent = true; + bool allColsAreFixed = true; + bool haveVariable = false; + int fixedWidth = 0; +#ifdef DEBUG_LAYOUT + int cSpan = span; +#endif + while ( lastCol < nEffCols && span > 0 ) { + switch( layoutStruct[lastCol].width.type() ) { + case Percent: + totalPercent += layoutStruct[lastCol].width.value(); + allColsAreFixed = false; + break; + case Fixed: + if (layoutStruct[lastCol].width.value() > 0) { + fixedWidth += layoutStruct[lastCol].width.value(); + allColsArePercent = false; + // IE resets effWidth to Variable here, but this breaks the konqueror about page and seems to be some bad + // legacy behavior anyway. mozilla doesn't do this so I decided we don't either. + break; + } + // fall through + case Variable: + haveVariable = true; + // fall through + default: + // If the column is a percentage width, do not let the spanning cell overwrite the + // width value. This caused a mis-rendering on amazon.com. + // Sample snippet: + // <table border=2 width=100%>< + // <tr><td>1</td><td colspan=2>2-3</tr> + // <tr><td>1</td><td colspan=2 width=100%>2-3</td></tr> + // </table> + if (!layoutStruct[lastCol].effWidth.isPercent()) { + layoutStruct[lastCol].effWidth = Length(); + allColsArePercent = false; + } + else + totalPercent += layoutStruct[lastCol].effWidth.value(); + allColsAreFixed = false; + } + span -= table->spanOfEffCol( lastCol ); + minWidth += layoutStruct[lastCol].effMinWidth; + maxWidth += layoutStruct[lastCol].effMaxWidth; + lastCol++; + cMinWidth -= hspacing; + cMaxWidth -= hspacing; + } +#ifdef DEBUG_LAYOUT + tqDebug(" colspan cell %p at effCol %d, span %d, type %d, value %d cmin=%d min=%d fixedwidth=%d", cell, col, cSpan, w.type(), w.value(), cMinWidth, minWidth, fixedWidth ); +#endif + + // adjust table max width if needed + if ( w.isPercent() ) { + if ( totalPercent > w.value() || allColsArePercent ) { + // can't satify this condition, treat as variable + w = Length(); + } else { + int spanMax = kMax( maxWidth, cMaxWidth ); +#ifdef DEBUG_LAYOUT + tqDebug(" adjusting tMaxWidth (%d): spanMax=%d, value=%d, totalPercent=%d", tMaxWidth, spanMax, w.value(), totalPercent ); +#endif + tMaxWidth = kMax( tMaxWidth, spanMax * 100 / w.value() ); + + // all non percent columns in the span get percent values to sum up correctly. + int percentMissing = w.value() - totalPercent; + int totalWidth = 0; + for ( unsigned int pos = col; pos < lastCol; pos++ ) { + if ( !(layoutStruct[pos].width.isPercent() ) ) + totalWidth += layoutStruct[pos].effMaxWidth; + } + + for ( unsigned int pos = col; pos < lastCol && totalWidth > 0; pos++ ) { + if ( !(layoutStruct[pos].width.isPercent() ) ) { + int percent = percentMissing * layoutStruct[pos].effMaxWidth / totalWidth; +#ifdef DEBUG_LAYOUT + tqDebug(" col %d: setting percent value %d effMaxWidth=%d totalWidth=%d", pos, percent, layoutStruct[pos].effMaxWidth, totalWidth ); +#endif + totalWidth -= layoutStruct[pos].effMaxWidth; + percentMissing -= percent; + if ( percent > 0 ) + layoutStruct[pos].effWidth = Length( percent, Percent ); + else + layoutStruct[pos].effWidth = Length(); + } + } + + } + } + + // make sure minWidth and maxWidth of the spanning cell are honoured + if ( cMinWidth > minWidth ) { + if ( allColsAreFixed ) { +#ifdef DEBUG_LAYOUT + tqDebug("extending minWidth of cols %d-%d to %dpx currentMin=%d accroding to fixed sum %d", col, lastCol-1, cMinWidth, minWidth, fixedWidth ); +#endif + for ( unsigned int pos = col; fixedWidth > 0 && pos < lastCol; pos++ ) { + int w = kMax( int( layoutStruct[pos].effMinWidth ), cMinWidth * layoutStruct[pos].width.value() / fixedWidth ); +#ifdef DEBUG_LAYOUT + tqDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w ); +#endif + fixedWidth -= layoutStruct[pos].width.value(); + cMinWidth -= w; + layoutStruct[pos].effMinWidth = w; + } + + } else if ( allColsArePercent ) { + int maxw = maxWidth; + int minw = minWidth; + int cminw = cMinWidth; + + for ( unsigned int pos = col; maxw > 0 && pos < lastCol; pos++ ) { + if ( layoutStruct[pos].effWidth.isPercent() && layoutStruct[pos].effWidth.value()>0 && fixedWidth <= cMinWidth) { + int w = layoutStruct[pos].effMinWidth; + w = kMax( w, cminw*layoutStruct[pos].effWidth.value()/totalPercent ); + w = kMin(layoutStruct[pos].effMinWidth+(cMinWidth-minw), w); +#ifdef DEBUG_LAYOUT + tqDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w ); +#endif + maxw -= layoutStruct[pos].effMaxWidth; + minw -= layoutStruct[pos].effMinWidth; + cMinWidth -= w; + layoutStruct[pos].effMinWidth = w; + } + } + } else { +#ifdef DEBUG_LAYOUT + tqDebug("extending minWidth of cols %d-%d to %dpx currentMin=%d", col, lastCol-1, cMinWidth, minWidth ); +#endif + int maxw = maxWidth; + int minw = minWidth; + + // Give min to variable first, to fixed second, and to others third. + for ( unsigned int pos = col; maxw > 0 && pos < lastCol; pos++ ) { + if ( layoutStruct[pos].width.isFixed() && haveVariable && fixedWidth <= cMinWidth ) { + int w = kMax( int( layoutStruct[pos].effMinWidth ), layoutStruct[pos].width.value() ); + fixedWidth -= layoutStruct[pos].width.value(); + minw -= layoutStruct[pos].effMinWidth; +#ifdef DEBUG_LAYOUT + tqDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w ); +#endif + maxw -= layoutStruct[pos].effMaxWidth; + cMinWidth -= w; + layoutStruct[pos].effMinWidth = w; + } + } + + for ( unsigned int pos = col; maxw > 0 && pos < lastCol && minw < cMinWidth; pos++ ) { + if ( !(layoutStruct[pos].width.isFixed() && haveVariable && fixedWidth <= cMinWidth) ) { + int w = kMax( int( layoutStruct[pos].effMinWidth ), cMinWidth * layoutStruct[pos].effMaxWidth / maxw ); + w = kMin(layoutStruct[pos].effMinWidth+(cMinWidth-minw), w); + +#ifdef DEBUG_LAYOUT + tqDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w ); +#endif + maxw -= layoutStruct[pos].effMaxWidth; + minw -= layoutStruct[pos].effMinWidth; + cMinWidth -= w; + layoutStruct[pos].effMinWidth = w; + } + } + } + } + if ( !w.isPercent() ) { + if ( cMaxWidth > maxWidth ) { +#ifdef DEBUG_LAYOUT + tqDebug("extending maxWidth of cols %d-%d to %dpx", col, lastCol-1, cMaxWidth ); +#endif + for ( unsigned int pos = col; maxWidth > 0 && pos < lastCol; pos++ ) { + int w = kMax( int( layoutStruct[pos].effMaxWidth ), cMaxWidth * layoutStruct[pos].effMaxWidth / maxWidth ); +#ifdef DEBUG_LAYOUT + tqDebug(" col %d: max=%d, effMax=%d, new=%d", pos, layoutStruct[pos].effMaxWidth, layoutStruct[pos].effMaxWidth, w ); +#endif + maxWidth -= layoutStruct[pos].effMaxWidth; + cMaxWidth -= w; + layoutStruct[pos].effMaxWidth = w; + } + } + } else { + for ( unsigned int pos = col; pos < lastCol; pos++ ) + layoutStruct[pos].maxWidth = kMax(layoutStruct[pos].maxWidth, int(layoutStruct[pos].minWidth) ); + } + } + effWidthDirty = false; + +// tqDebug("calcEffectiveWidth: tMaxWidth=%d", tMaxWidth ); + return tMaxWidth; +} + +/* gets all cells that originate in a column and have a cellspan > 1 + Sorts them by increasing cellspan +*/ +void AutoTableLayout::insertSpanCell( RenderTableCell *cell ) +{ + if ( !cell || cell == (RenderTableCell *)-1 || cell->colSpan() == 1 ) + return; + +// tqDebug("inserting span cell %p with span %d", cell, cell->colSpan() ); + int size = spanCells.size(); + if ( !size || spanCells[size-1] != 0 ) { + spanCells.resize( size + 10 ); + for ( int i = 0; i < 10; i++ ) + spanCells[size+i] = 0; + size += 10; + } + + // add them in sort. This is a slow algorithm, and a binary search or a fast sorting after collection would be better + unsigned int pos = 0; + int span = cell->colSpan(); + while ( pos < spanCells.size() && spanCells[pos] && span > spanCells[pos]->colSpan() ) + pos++; + memmove( spanCells.data()+pos+1, spanCells.data()+pos, (size-pos-1)*sizeof( RenderTableCell * ) ); + spanCells[pos] = cell; +} + + +void AutoTableLayout::layout() +{ + // table layout based on the values collected in the layout structure. + int tableWidth = table->width() - table->bordersPaddingAndSpacing(); + int available = tableWidth; + int nEffCols = table->numEffCols(); + + if ( nEffCols != (int)layoutStruct.size() ) { + tqWarning("WARNING: nEffCols is not equal to layoutstruct!" ); + fullRecalc(); + nEffCols = table->numEffCols(); + } +#ifdef DEBUG_LAYOUT + tqDebug("AutoTableLayout::layout()"); +#endif + + if ( effWidthDirty ) + calcEffectiveWidth(); + +#ifdef DEBUG_LAYOUT + tqDebug(" tableWidth=%d, nEffCols=%d", tableWidth, nEffCols ); + for ( int i = 0; i < nEffCols; i++ ) { + tqDebug(" effcol %d is of type %d value %d, minWidth=%d, maxWidth=%d", + i, layoutStruct[i].width.type(), layoutStruct[i].width.value(), + layoutStruct[i].minWidth, layoutStruct[i].maxWidth ); + tqDebug(" effective: type %d value %d, minWidth=%d, maxWidth=%d", + layoutStruct[i].effWidth.type(), layoutStruct[i].effWidth.value(), + layoutStruct[i].effMinWidth, layoutStruct[i].effMaxWidth ); + } +#endif + + bool havePercent = false; + bool haveRelative = false; + int totalRelative = 0; + int numVariable = 0; + int numFixed = 0; + int totalVariable = 0; + int totalFixed = 0; + int totalPercent = 0; + int allocVariable = 0; + + // fill up every cell with it's minWidth + for ( int i = 0; i < nEffCols; i++ ) { + int w = layoutStruct[i].effMinWidth; + layoutStruct[i].calcWidth = w; + available -= w; + Length& width = layoutStruct[i].effWidth; + switch( width.type()) { + case Percent: + havePercent = true; + totalPercent += width.value(); + break; + case Relative: + haveRelative = true; + totalRelative += width.value(); + break; + case Fixed: + numFixed++; + totalFixed += layoutStruct[i].effMaxWidth; + // fall through + break; + case Variable: + case Static: + numVariable++; + totalVariable += layoutStruct[i].effMaxWidth; + allocVariable += w; + } + } + + // allocate width to percent cols + if ( available > 0 && havePercent ) { + for ( int i = 0; i < nEffCols; i++ ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isPercent() ) { + int w = kMax ( int( layoutStruct[i].effMinWidth ), width.minWidth( tableWidth ) ); + available += layoutStruct[i].calcWidth - w; + layoutStruct[i].calcWidth = w; + } + } + if ( totalPercent > 100 ) { + // remove overallocated space from the last columns + int excess = tableWidth*(totalPercent-100)/100; + for ( int i = nEffCols-1; i >= 0; i-- ) { + if ( layoutStruct[i].effWidth.isPercent() ) { + int w = layoutStruct[i].calcWidth; + int reduction = kMin( w, excess ); + // the lines below might look inconsistent, but that's the way it's handled in mozilla + excess -= reduction; + int newWidth = kMax( int (layoutStruct[i].effMinWidth), w - reduction ); + available += w - newWidth; + layoutStruct[i].calcWidth = newWidth; + //tqDebug("col %d: reducing to %d px (reduction=%d)", i, newWidth, reduction ); + } + } + } + } +#ifdef DEBUG_LAYOUT + tqDebug("percent satisfied: available is %d", available); +#endif + + // then allocate width to fixed cols + if ( available > 0 ) { + for ( int i = 0; i < nEffCols; ++i ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isFixed() && width.value() > layoutStruct[i].calcWidth ) { + available += layoutStruct[i].calcWidth - width.value(); + layoutStruct[i].calcWidth = width.value(); + } + } + } +#ifdef DEBUG_LAYOUT + tqDebug("fixed satisfied: available is %d", available); +#endif + + // now satisfy relative + if ( available > 0 ) { + for ( int i = 0; i < nEffCols; i++ ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isRelative() && width.value() ) { + // width=0* gets effMinWidth. + int w = width.value()*tableWidth/totalRelative; + available += layoutStruct[i].calcWidth - w; + layoutStruct[i].calcWidth = w; + } + } + } + + // now satisfy variable + if ( available > 0 && numVariable ) { + available += allocVariable; // this gets redistributed + //tqDebug("redistributing %dpx to %d variable columns. totalVariable=%d", available, numVariable, totalVariable ); + for ( int i = 0; i < nEffCols; i++ ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isVariable() && totalVariable != 0 ) { + int w = kMax( int ( layoutStruct[i].calcWidth ), + available * layoutStruct[i].effMaxWidth / totalVariable ); + available -= w; + totalVariable -= layoutStruct[i].effMaxWidth; + layoutStruct[i].calcWidth = w; + } + } + } +#ifdef DEBUG_LAYOUT + tqDebug("variable satisfied: available is %d", available ); +#endif + + // spread over fixed colums + if ( available > 0 && numFixed) { + // still have some width to spread, distribute to fixed columns + for ( int i = 0; i < nEffCols; i++ ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isFixed() ) { + int w = available * layoutStruct[i].effMaxWidth / totalFixed; + available -= w; + totalFixed -= layoutStruct[i].effMaxWidth; + layoutStruct[i].calcWidth += w; + } + } + } + +#ifdef DEBUG_LAYOUT + tqDebug("after fixed distribution: available=%d", available ); +#endif + + // spread over percent colums + if ( available > 0 && hasPercent && totalPercent < 100) { + // still have some width to spread, distribute weighted to percent columns + for ( int i = 0; i < nEffCols; i++ ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isPercent() ) { + int w = available * width.value() / totalPercent; + available -= w; + totalPercent -= width.value(); + layoutStruct[i].calcWidth += w; + if (!available || !totalPercent) break; + } + } + } + +#ifdef DEBUG_LAYOUT + tqDebug("after percent distribution: available=%d", available ); +#endif + + // spread over the rest + if ( available > 0 ) { + int total = nEffCols; + // still have some width to spread + int i = nEffCols; + while ( i-- ) { + int w = available / total; + available -= w; + total--; + layoutStruct[i].calcWidth += w; + } + } + +#ifdef DEBUG_LAYOUT + tqDebug("after equal distribution: available=%d", available ); +#endif + // if we have overallocated, reduce every cell according to the difference between desired width and minwidth + // this seems to produce to the pixel exaxt results with IE. Wonder is some of this also holds for width distributing. + if ( available < 0 ) { + // Need to reduce cells with the following prioritization: + // (1) Variable + // (2) Relative + // (3) Fixed + // (4) Percent + // This is basically the reverse of how we grew the cells. + if (available < 0) { + int mw = 0; + for ( int i = nEffCols-1; i >= 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isVariable()) + mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth; + } + + for ( int i = nEffCols-1; i >= 0 && mw > 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isVariable()) { + int minMaxDiff = layoutStruct[i].calcWidth-layoutStruct[i].effMinWidth; + int reduce = available * minMaxDiff / mw; + layoutStruct[i].calcWidth += reduce; + available -= reduce; + mw -= minMaxDiff; + if ( available >= 0 ) + break; + } + } + } + + if (available < 0) { + int mw = 0; + for ( int i = nEffCols-1; i >= 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isRelative()) + mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth; + } + + for ( int i = nEffCols-1; i >= 0 && mw > 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isRelative()) { + int minMaxDiff = layoutStruct[i].calcWidth-layoutStruct[i].effMinWidth; + int reduce = available * minMaxDiff / mw; + layoutStruct[i].calcWidth += reduce; + available -= reduce; + mw -= minMaxDiff; + if ( available >= 0 ) + break; + } + } + } + + if (available < 0) { + int mw = 0; + for ( int i = nEffCols-1; i >= 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isFixed()) + mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth; + } + + for ( int i = nEffCols-1; i >= 0 && mw > 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isFixed()) { + int minMaxDiff = layoutStruct[i].calcWidth-layoutStruct[i].effMinWidth; + int reduce = available * minMaxDiff / mw; + layoutStruct[i].calcWidth += reduce; + available -= reduce; + mw -= minMaxDiff; + if ( available >= 0 ) + break; + } + } + } + + if (available < 0) { + int mw = 0; + for ( int i = nEffCols-1; i >= 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isPercent()) + mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth; + } + + for ( int i = nEffCols-1; i >= 0 && mw > 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isPercent()) { + int minMaxDiff = layoutStruct[i].calcWidth-layoutStruct[i].effMinWidth; + int reduce = available * minMaxDiff / mw; + layoutStruct[i].calcWidth += reduce; + available -= reduce; + mw -= minMaxDiff; + if ( available >= 0 ) + break; + } + } + } + } + + //tqDebug( " final available=%d", available ); + + int pos = 0; + for ( int i = 0; i < nEffCols; i++ ) { +#ifdef DEBUG_LAYOUT + tqDebug("col %d: %d (width %d)", i, pos, layoutStruct[i].calcWidth ); +#endif + table->columnPos[i] = pos; + pos += layoutStruct[i].calcWidth + table->borderHSpacing(); + } + table->columnPos[table->columnPos.size()-1] = pos; + +} + + +void AutoTableLayout::calcPercentages() const +{ + total_percent = 0; + for ( unsigned int i = 0; i < layoutStruct.size(); i++ ) { + if ( layoutStruct[i].width.isPercent() ) + total_percent += layoutStruct[i].width.value(); + } + percentagesDirty = false; +} + +#undef DEBUG_LAYOUT |