Files
flenser/dw/table.cc
ADAM David Alan Martin fad69219ad
Some checks failed
CI / ubuntu-latest-html-tests (push) Has been cancelled
CI / ubuntu-latest-no-tls (push) Has been cancelled
CI / ubuntu-latest-mbedtls2 (push) Has been cancelled
CI / ubuntu-latest-openssl-3 (push) Has been cancelled
CI / ubuntu-latest-with-old-std (push) Has been cancelled
CI / ubuntu-20-04-openssl-1-1 (push) Has been cancelled
CI / alpine-mbedtls-3_6_0 (push) Has been cancelled
CI / macOS-13-openssl-1-1 (push) Has been cancelled
CI / macOS-13-openssl-3 (push) Has been cancelled
CI / freebsd-14-openssl-3 (push) Has been cancelled
CI / windows-mbedtls (push) Has been cancelled
A few more SimpleVector changes.
2025-08-11 02:30:11 -04:00

1646 lines
55 KiB
C++

/*
* Dillo Widget
*
* Copyright 2005-2007, 2014 Sebastian Geerken <sgeerken@dillo.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#define DBG
#include "table.hh"
#include "../lout/msg.h"
#include "../lout/misc.hh"
#include "../lout/debug.hh"
using namespace lout;
namespace dw {
bool Table::adjustTableMinWidth = true;
Table::Table(bool limitTextWidth)
{
DBG_OBJ_CREATE ("dw::Table");
registerName ("dw::Table", typeid(*this));
setButtonSensitive(false);
this->limitTextWidth = limitTextWidth;
rowClosed = false;
numRows = 0;
numCols = 0;
curRow = -1;
curCol = 0;
DBG_OBJ_SET_NUM ("numCols", numCols);
DBG_OBJ_SET_NUM ("numRows", numCols);
children = new misc::SimpleVector <Child*> (16);
colExtremes = new misc::SimpleVector<core::Extremes> (8);
colWidthSpecified = new misc::SimpleVector<bool> (8);
colWidthPercentage = new misc::SimpleVector<bool> (8);
rowSpanCells = new misc::SimpleVector <int> (8);
baseline = new misc::SimpleVector <int> (8);
rowStyle = new misc::SimpleVector <core::style::Style*> (8);
colWidthsUpToDateWidthColExtremes = true;
DBG_OBJ_SET_BOOL ("colWidthsUpToDateWidthColExtremes",
colWidthsUpToDateWidthColExtremes);
numColWidthSpecified = 0;
numColWidthPercentage = 0;
redrawX = 0;
redrawY = 0;
}
Table::~Table()
{
for (int i = 0; i < children->size (); i++) {
if (children->get(i)) {
switch (children->get(i)->type) {
case Child::CELL:
delete children->get(i)->cell.widget;
break;
case Child::SPAN_SPACE:
break;
}
delete children->get(i);
}
}
for (int i = 0; i < rowStyle->size (); i++)
if (rowStyle->get (i))
rowStyle->get(i)->unref ();
delete children;
delete colExtremes;
delete colWidthSpecified;
delete colWidthPercentage;
delete rowSpanCells;
delete baseline;
delete rowStyle;
DBG_OBJ_DELETE ();
}
void Table::sizeRequestSimpl (core::Requisition *requisition)
{
DBG_OBJ_ENTER0 ("resize", 0, "sizeRequestImpl");
forceCalcCellSizes (true);
/**
* \bug Baselines are not regarded here.
*/
requisition->width =
boxDiffWidth () + (numCols + 1) * getStyle()->hBorderSpacing;
for (int col = 0; col < numCols; col++)
requisition->width += colWidths.at (col);
requisition->ascent =
boxDiffHeight () + cumHeight.at (numRows) + getStyle()->vBorderSpacing;
requisition->descent = 0;
correctRequisition (requisition, core::splitHeightPreserveDescent, true,
false);
// For the order, see similar reasoning for dw::Textblock.
correctRequisitionByOOF (requisition, core::splitHeightPreserveDescent);
DBG_OBJ_LEAVE ();
}
void Table::getExtremesSimpl (core::Extremes *extremes)
{
DBG_OBJ_ENTER0 ("resize", 0, "getExtremesImpl");
if (numCols == 0)
extremes->minWidth = extremes->minWidthIntrinsic = extremes->maxWidth =
extremes->maxWidthIntrinsic = extremes->adjustmentWidth =
boxDiffWidth ();
else {
forceCalcColumnExtremes ();
extremes->minWidth = extremes->minWidthIntrinsic = extremes->maxWidth =
extremes->maxWidthIntrinsic = extremes->adjustmentWidth =
(numCols + 1) * getStyle()->hBorderSpacing + boxDiffWidth ();
for (int col = 0; col < numCols; col++) {
extremes->minWidth += colExtremes->getRef(col)->minWidth;
extremes->minWidthIntrinsic +=
colExtremes->getRef(col)->minWidthIntrinsic;
extremes->maxWidth += colExtremes->getRef(col)->maxWidth;
extremes->maxWidthIntrinsic +=
colExtremes->getRef(col)->maxWidthIntrinsic;
extremes->adjustmentWidth += colExtremes->getRef(col)->adjustmentWidth;
}
}
correctExtremes (extremes, true);
// For the order, see similar reasoning for dw::Textblock.
correctExtremesByOOF (extremes);
DBG_OBJ_LEAVE ();
}
void Table::sizeAllocateImpl (core::Allocation *allocation)
{
DBG_OBJ_ENTER ("resize", 0, "sizeAllocateImpl", "%d, %d; %d * (%d + %d)",
allocation->x, allocation->y, allocation->width,
allocation->ascent, allocation->descent);
sizeAllocateStart (allocation);
calcCellSizes (true);
/**
* \bug Baselines are not regarded here.
*/
int offy = allocation->y + boxOffsetY () + getStyle()->vBorderSpacing;
int x = allocation->x + boxOffsetX () + getStyle()->hBorderSpacing;
for (int col = 0; col < numCols; col++) {
for (int row = 0; row < numRows; row++) {
int n = row * numCols + col;
if (childDefined (n)) {
int width = (children->get(n)->cell.colspanEff - 1)
* getStyle()->hBorderSpacing;
for (int i = 0; i < children->get(n)->cell.colspanEff; i++)
width += colWidths.at (col + i);
core::Allocation childAllocation;
core::Requisition childRequisition;
children->get(n)->cell.widget->sizeRequest (&childRequisition);
childAllocation.x = x;
childAllocation.y = cumHeight.at (row) + offy;
childAllocation.width = width;
childAllocation.ascent = childRequisition.ascent;
childAllocation.descent =
cumHeight.at (row + children->get(n)->cell.rowspan)
- cumHeight.at (row) - getStyle()->vBorderSpacing
- childRequisition.ascent;
children->get(n)->cell.widget->sizeAllocate (&childAllocation);
}
}
x += colWidths.at (col) + getStyle()->hBorderSpacing;
}
sizeAllocateEnd ();
DBG_OBJ_LEAVE ();
}
void Table::resizeDrawImpl ()
{
queueDrawArea (redrawX, 0, allocation.width - redrawX, getHeight ());
queueDrawArea (0, redrawY, allocation.width, getHeight () - redrawY);
redrawX = allocation.width;
redrawY = getHeight ();
}
int Table::getAvailWidthOfChild (Widget *child, bool forceValue)
{
DBG_OBJ_ENTER ("resize", 0, "getAvailWidthOfChild", "%p, %s",
child, forceValue ? "true" : "false");
int width;
oof::OutOfFlowMgr *oofm;
if (isWidgetOOF(child) && (oofm = getWidgetOutOfFlowMgr(child)) &&
oofm->dealingWithSizeOfChild (child))
width = oofm->getAvailWidthOfChild (child, forceValue);
else {
// We do not calculate the column widths at this point, because
// this tends to be rather inefficient for tables with many
// cells:
//
// For each of the n cells, some text is added (say, only one word
// per cell). Textblock::addText will eventually (via addText0
// etc.) call this method, Table::getAvailWidthOfChild. If
// calcCellSizes() is called here, this will call
// forceCalcCellSizes(), since the last call, sizes have to be
// re-calculated (because cells have been added). This will
// calculate the extremes for each existing cell, so
// Widget::getExtremes is called n * (n + 1) / 2 times. Even if the
// extremes are cached (so that getExtremesImpl does not have to be
// called in each case), this would make rendering tables with more
// than a few hundred cells unacceptably slow.
//
// Instead, column widths are calculated in Table::sizeRequestImpl.
//
// An alternative would be incremental resizing for tables; this
// approach resembles the behaviour before GROWS.
// TODO Does it still make sense to return -1 when forceValue is
// set?
if (forceValue)
width = calcAvailWidthForDescendant (child);
else
width = -1;
}
DBG_OBJ_MSGF ("resize", 1, "=> %d", width);
DBG_OBJ_LEAVE ();
return width;
}
int Table::calcAvailWidthForDescendant (Widget *child)
{
DBG_OBJ_ENTER ("resize", 0, "calcAvailWidthForDescendant", "%p", child);
// "child" is not a direct child, but a direct descendant. Search
// for the actual children.
Widget *actualChild = child;
while (actualChild != NULL && actualChild->getParent () != this)
actualChild = actualChild->getParent ();
assert (actualChild != NULL);
// ActualChild->parentRef contains (indirectly) the position in the
// children array (see addCell()), so the column can be easily
// determined.
int childNo = getParentRefInFlowSubRef (actualChild->parentRef);
int col = childNo % numCols;
DBG_OBJ_MSGF ("resize", 1, "actualChild = %p, "
"childNo = getParentRefInFlowSubRef (%d) = %d, "
"column = %d %% %d = %d",
actualChild, actualChild->parentRef, childNo, childNo,
numCols, col);
int colspanEff = children->get(childNo)->cell.colspanEff;
DBG_OBJ_MSGF ("resize", 1, "calculated from column %d, colspanEff = %d",
col, colspanEff);
int width = (colspanEff - 1) * getStyle()->hBorderSpacing;
for (int i = 0; i < colspanEff; i++)
width += colWidths.at (col + i);
width = std::max (width, 0);
if (child != actualChild) {
// For table cells (direct children: child == actualChild), CSS
// 'width' is already regarded in the column calculation.
// However, for children of the table cells, CSS 'width' must be
// regarded here.
int corrWidth = width;
child->calcFinalWidth (child->getStyle(), -1, this, 0, true, &corrWidth);
// But better not exceed it ... (TODO: Only here?)
width = std::min (width, corrWidth);
}
DBG_OBJ_MSGF ("resize", 1, "=> %d", width);
DBG_OBJ_LEAVE ();
return width;
}
int Table::applyPerWidth (int containerWidth, core::style::Length perWidth)
{
return core::style::multiplyWithPerLength (containerWidth, perWidth);
}
int Table::applyPerHeight (int containerHeight, core::style::Length perHeight)
{
return core::style::multiplyWithPerLength (containerHeight, perHeight);
}
void Table::containerSizeChangedForChildren ()
{
DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren");
for (int col = 0; col < numCols; col++) {
for (int row = 0; row < numRows; row++) {
int n = row * numCols + col;
if (childDefined (n))
children->get(n)->cell.widget->containerSizeChanged ();
}
}
containerSizeChangedForChildrenOOF ();
DBG_OBJ_LEAVE ();
}
bool Table::affectsSizeChangeContainerChild (core::Widget *child)
{
DBG_OBJ_ENTER ("resize", 0, "affectsSizeChangeContainerChild", "%p", child);
bool ret;
// This is a bit more complicated, as compared to the standard
// implementation (Widget::affectsSizeChangeContainerChild).
// Height would handled the same way, but width is more
// complicated: we would have to track numerous values here. Always
// returning true is correct in all cases, but generally
// inefficient.
// TODO Better solution?
ret = true;
DBG_OBJ_MSGF ("resize", 1, "=> %s", ret ? "true" : "false");
DBG_OBJ_LEAVE ();
return ret;
}
bool Table::usesAvailWidth ()
{
return true;
}
bool Table::isBlockLevel ()
{
return true;
}
void Table::drawLevel (core::View *view, core::Rectangle *area, int level,
core::DrawingContext *context)
{
DBG_OBJ_ENTER ("draw", 0, "Table::drawLevel", "[%d, %d, %d * %d], %s",
area->x, area->y, area->width, area->height,
stackingLevelText (level));
#if 0
// This old code belongs perhaps to the background. Check when reactivated.
int offx = getStyle()->boxOffsetX () + getStyle()->hBorderSpacing;
int offy = getStyle()->boxOffsetY () + getStyle()->vBorderSpacing;
int width = getContentWidth ();
// This part seems unnecessary. It also segfaulted sometimes when
// cumHeight size was less than numRows. --jcid
for (int row = 0; row < numRows; row++) {
if (rowStyle->get (row))
drawBox (view, rowStyle->get (row), area,
offx, offy + cumHeight->get (row),
width - 2*getStyle()->hBorderSpacing,
cumHeight->get (row + 1) - cumHeight->get (row)
- getStyle()->vBorderSpacing, false);
}
#endif
switch (level) {
case SL_IN_FLOW:
for (int i = 0; i < children->size (); i++) {
if (childDefined (i)) {
Widget *child = children->get(i)->cell.widget;
core::Rectangle childArea;
if (!core::StackingContextMgr::handledByStackingContextMgr (child)
&& child->intersects (this, area, &childArea))
child->draw (view, &childArea, context);
}
}
break;
default:
OOFAwareWidget::drawLevel (view, area, level, context);
break;
}
DBG_OBJ_LEAVE ();
}
core::Widget *Table::getWidgetAtPointLevel (int x, int y, int level,
core::GettingWidgetAtPointContext
*context)
{
DBG_OBJ_ENTER ("events", 0, "Table::getWidgetAtPointLevel", "%d, %d, %s",
x, y, stackingLevelText (level));
Widget *widgetAtPoint = NULL;
switch (level) {
case SL_IN_FLOW:
for (int i = children->size () - 1; widgetAtPoint == NULL && i >= 0;
i--) {
if (childDefined (i)) {
Widget *child = children->get(i)->cell.widget;
if (!core::StackingContextMgr::handledByStackingContextMgr (child))
widgetAtPoint = child->getWidgetAtPoint (x, y, context);
}
}
break;
default:
widgetAtPoint =
OOFAwareWidget::getWidgetAtPointLevel (x, y, level, context);
break;
}
DBG_OBJ_MSGF ("events", 1, "=> %p", widgetAtPoint);
DBG_OBJ_LEAVE ();
return widgetAtPoint;
}
void Table::removeChild (Widget *child)
{
/** \bug Not implemented. */
}
core::Iterator *Table::iterator (core::Content::Type mask, bool atEnd)
{
return new TableIterator (this, mask, atEnd);
}
void Table::addCell (Widget *widget, int colspan, int rowspan)
{
DBG_OBJ_ENTER ("resize", 0, "addCell", "%p, %d, %d",
widget, colspan, rowspan);
const int maxspan = 100;
Child *child;
int colspanEff;
// We limit the values for colspan and rowspan to avoid
// attacks by malicious web pages.
if (colspan > maxspan || colspan < 0) {
MSG_WARN("colspan = %d is set to %d.\n", colspan, maxspan);
colspan = maxspan;
}
if (rowspan > maxspan || rowspan <= 0) {
MSG_WARN("rowspan = %d is set to %d.\n", rowspan, maxspan);
rowspan = maxspan;
}
if (numRows == 0) {
// to prevent a crash
MSG("addCell: cell without row.\n");
addRow (NULL);
}
if (rowClosed) {
MSG_WARN("Last cell had colspan=0.\n");
addRow (NULL);
}
if (colspan == 0) {
colspanEff = std::max (numCols - curCol, 1);
rowClosed = true;
} else
colspanEff = colspan;
// Find next free cell-
while (curCol < numCols &&
(child = children->get(curRow * numCols + curCol)) != NULL &&
child->type == Child::SPAN_SPACE)
curCol++;
_MSG("Table::addCell numCols=%d,curCol=%d,colspan=%d,colspanEff=%d\n",
numCols, curCol, colspan, colspanEff);
// Increase children array, when necessary.
if (curRow + rowspan > numRows)
reallocChildren (numCols, curRow + rowspan);
if (curCol + colspanEff > numCols)
reallocChildren (curCol + colspanEff, numRows);
// Fill span space.
for (int col = 0; col < colspanEff; col++)
for (int row = 0; row < rowspan; row++)
if (!(col == 0 && row == 0)) {
int i = (curRow + row) * numCols + curCol + col;
child = children->get(i);
if (child) {
MSG("Overlapping spans in table.\n");
assert(child->type == Child::SPAN_SPACE);
delete child;
}
child = new Child ();
child->type = Child::SPAN_SPACE;
child->spanSpace.startCol = curCol;
child->spanSpace.startRow = curRow;
children->set (i, child);
}
// Set the "root" cell.
child = new Child ();
child->type = Child::CELL;
child->cell.widget = widget;
child->cell.colspanOrig = colspan;
child->cell.colspanEff = colspanEff;
child->cell.rowspan = rowspan;
children->set (curRow * numCols + curCol, child);
// The position in the children array is (indirectly) assigned to parentRef,
// although incremental resizing is not implemented. Useful, e. g., in
// calcAvailWidthForDescendant(). See also reallocChildren().
widget->parentRef = makeParentRefInFlow (curRow * numCols + curCol);
DBG_OBJ_SET_NUM_O (widget, "parentRef", widget->parentRef);
curCol += colspanEff;
widget->setParent (this);
if (rowStyle->get (curRow))
widget->setBgColor (rowStyle->get(curRow)->backgroundColor);
queueResize (0, true);
#if 0
// show table structure in stdout
for (int row = 0; row < numRows; row++) {
for (int col = 0; col < numCols; col++) {
int n = row * numCols + col;
if (!(child = children->get (n))) {
MSG("[null ] ");
} else if (children->get(n)->type == Child::CELL) {
MSG("[CELL rs=%d] ", child->cell.rowspan);
} else if (children->get(n)->type == Child::SPAN_SPACE) {
MSG("[SPAN rs=%d] ", child->cell.rowspan);
} else {
MSG("[Unk. ] ");
}
}
MSG("\n");
}
MSG("\n");
#endif
DBG_OBJ_LEAVE ();
}
void Table::addRow (core::style::Style *style)
{
curRow++;
if (curRow >= numRows)
reallocChildren (numCols, curRow + 1);
if (rowStyle->get (curRow))
rowStyle->get(curRow)->unref ();
rowStyle->set (curRow, style);
if (style)
style->ref ();
curCol = 0;
rowClosed = false;
}
AlignedTableCell *Table::getCellRef ()
{
core::Widget *child;
for (int row = 0; row <= numRows; row++) {
int n = curCol + row * numCols;
if (childDefined (n)) {
child = children->get(n)->cell.widget;
if (auto *atc= dynamic_cast< AlignedTableCell * >( child ))
return atc;
}
}
return NULL;
}
const char *Table::getExtrModName (ExtrMod mod)
{
switch (mod) {
case MIN:
return "MIN";
case MIN_INTR:
return "MIN_INTR";
case MIN_MIN:
return "MIN_MIN";
case MAX_MIN:
return "MAX_MIN";
case MAX:
return "MAX";
case MAX_INTR:
return "MAX_INTR";
case DATA:
return "DATA";
default:
misc::assertNotReached ();
return NULL;
}
}
int Table::getExtreme (core::Extremes *extremes, ExtrMod mod)
{
switch (mod) {
case MIN:
return extremes->minWidth;
case MIN_INTR:
return extremes->minWidthIntrinsic;
case MIN_MIN:
return std::min (extremes->minWidth, extremes->minWidthIntrinsic);
case MAX_MIN:
return std::max (extremes->minWidth, extremes->minWidthIntrinsic);
case MAX:
return extremes->maxWidth;
case MAX_INTR:
return extremes->maxWidthIntrinsic;
default:
misc::assertNotReached ();
return 0;
}
}
void Table::setExtreme (core::Extremes *extremes, ExtrMod mod, int value)
{
switch (mod) {
case MIN:
extremes->minWidth = value;
break;
case MIN_INTR:
extremes->minWidthIntrinsic = value;
break;
// MIN_MIN and MAX_MIN not supported here.
case MAX:
extremes->maxWidth = value;
break;
case MAX_INTR:
extremes->maxWidthIntrinsic = value;
break;
default:
misc::assertNotReached ();
}
}
int Table::getColExtreme (int col, ExtrMod mod, void *data)
{
switch (mod) {
case DATA:
return ((misc::SimpleVector<int>*)data)->get (col);
default:
return getExtreme (colExtremes->getRef(col), mod);
}
}
void Table::setColExtreme (int col, ExtrMod mod, void *data, int value)
{
switch (mod) {
case DATA:
((misc::SimpleVector<int>*)data)->set (col, value);
/* fallthrough */
default:
setExtreme (colExtremes->getRef(col), mod, value);
}
}
void Table::reallocChildren (int newNumCols, int newNumRows)
{
assert (newNumCols >= numCols);
assert (newNumRows >= numRows);
children->setSize (newNumCols * newNumRows);
if (newNumCols > numCols) {
// Complicated case, array got also wider.
for (int row = newNumRows - 1; row >= 0; row--) {
int colspan0Col = -1, colspan0Row = -1;
// Copy old part.
for (int col = numCols - 1; col >= 0; col--) {
int n = row * newNumCols + col;
children->set (n, children->get (row * numCols + col));
if (children->get (n)) {
switch (children->get(n)->type) {
case Child::CELL:
if (children->get(n)->cell.colspanOrig == 0) {
colspan0Col = col;
colspan0Row = row;
children->get(n)->cell.colspanEff = newNumCols - col;
}
break;
case Child::SPAN_SPACE:
if (children->get(children->get(n)->spanSpace.startRow
* numCols +
children->get(n)->spanSpace.startCol)
->cell.colspanOrig == 0) {
colspan0Col = children->get(n)->spanSpace.startCol;
colspan0Row = children->get(n)->spanSpace.startRow;
}
break;
}
}
}
// Fill rest of the column.
if (colspan0Col == -1) {
for (int col = numCols; col < newNumCols; col++)
children->set (row * newNumCols + col, NULL);
} else {
for (int col = numCols; col < newNumCols; col++) {
Child *child = new Child ();
child->type = Child::SPAN_SPACE;
child->spanSpace.startCol = colspan0Col;
child->spanSpace.startRow = colspan0Row;
children->set (row * newNumCols + col, child);
}
}
}
}
// Bottom part of the children array.
for (int row = numRows; row < newNumRows; row++)
for (int col = 0; col < newNumCols; col++)
children->set (row * newNumCols + col, NULL);
// Simple arrays.
rowStyle->setSize (newNumRows);
for (int row = numRows; row < newNumRows; row++)
rowStyle->set (row, NULL);
// Rest is increased, when needed.
if (newNumCols > numCols) {
// Re-calculate parentRef. See addCell().
for (int row = 1; row < newNumRows; row++)
for (int col = 0; col < newNumCols; col++) {
int n = row * newNumCols + col;
Child *child = children->get (n);
if (child != NULL && child->type == Child::CELL) {
child->cell.widget->parentRef = makeParentRefInFlow (n);
DBG_OBJ_SET_NUM_O (child->cell.widget, "parentRef",
child->cell.widget->parentRef);
}
}
}
numCols = newNumCols;
numRows = newNumRows;
// We initiate the column widths with a random value, to have a
// defined available width for the children before the column
// widths are actually calculated.
colWidths.resize( numCols, 100 );
DBG_IF_RTFL {
DBG_OBJ_SET_NUM ("colWidths.size", colWidths.size ());
for (int i = 0; i < colWidths.size(); i++)
DBG_OBJ_ARRSET_NUM ("colWidths", i, colWidths.at (i));
}
DBG_OBJ_SET_NUM ("numCols", numCols);
DBG_OBJ_SET_NUM ("numRows", numCols);
}
// ----------------------------------------------------------------------
void Table::calcCellSizes (bool calcHeights)
{
DBG_OBJ_ENTER ("resize", 0, "calcCellSizes", "%s",
calcHeights ? "true" : "false");
bool sizeChanged = needsResize () || resizeQueued ();
bool extremesChanget = extremesChanged () || extremesQueued ();
if (calcHeights ? (extremesChanget || sizeChanged) :
(extremesChanget || !colWidthsUpToDateWidthColExtremes))
forceCalcCellSizes (calcHeights);
DBG_OBJ_LEAVE ();
}
void Table::forceCalcCellSizes (bool calcHeights)
{
DBG_OBJ_ENTER ("resize", 0, "forceCalcCellSizes", "%s",
calcHeights ? "true" : "false");
// Since Table::getAvailWidthOfChild does not calculate the column
// widths, and so initially a random value (100) is returned, a
// correction is necessary. The old values are temporary preserved
// ...
std::vector<int> oldColWidths= colWidths;
actuallyCalcCellSizes (calcHeights);
// ... and then compared to the new ones. In case of a difference,
// the cell is told about this.
for (int col = 0; col < colWidths.size (); col++) {
if (oldColWidths.at (col) != colWidths.at (col)) {
for (int row = 0; row < numRows; row++) {
int n = row * numCols + col, col2;
Child *child = children->get(n);
if (child) {
Widget *cell;
switch (child->type) {
case Child::CELL:
cell = child->cell.widget;
break;
case Child::SPAN_SPACE:
// TODO Are Child::spanSpace::startRow and
// Child::spanSpace::startCol not defined?
// Search for actual cell. If not found, this means
// that a cell is spanning multiple columns *and*
// rows; in this case it has been processed before.
cell = NULL;
for (col2 = col - 1; col2 >= 0 && cell == NULL; col2--) {
int n2 = row * numCols + col2;
Child *child2 = children->get(n2);
if (child2 != NULL && child2->type == Child::CELL)
cell = child2->cell.widget;
}
break;
default:
misc::assertNotReached ();
cell = NULL;
}
if (cell)
cell->containerSizeChanged ();
}
}
}
}
DBG_OBJ_LEAVE ();
}
void Table::actuallyCalcCellSizes (bool calcHeights)
{
DBG_OBJ_ENTER ("resize", 0, "actuallyCalcCellSizes", "%s",
calcHeights ? "true" : "false");
int childHeight;
core::Extremes extremes;
// Will also call forceCalcColumnExtremes(), when needed.
getExtremes (&extremes);
int availWidth = getAvailWidth (true);
// When adjust_table_min_width is set, use perhaps the adjustment
// width for correction. (TODO: Is this necessary?)
int corrWidth =
Table::getAdjustTableMinWidth () ? extremes.adjustmentWidth : 0;
int totalWidth = std::max (availWidth, corrWidth)
- ((numCols + 1) * getStyle()->hBorderSpacing + boxDiffWidth ());
DBG_OBJ_MSGF ("resize", 1,
"totalWidth = max (%d, %d) - ((%d - 1) * %d + %d) = <b>%d</b>",
availWidth, corrWidth, numCols, getStyle()->hBorderSpacing,
boxDiffWidth (), totalWidth);
assert (colWidths.size () == numCols); // This is set in addCell.
cumHeight.resize (numRows + 1, 0);
rowSpanCells->setSize (0);
baseline->setSize (numRows);
std::vector< int > oldColWidths = colWidths;
colWidths.clear();
colWidths.resize( numCols );
int minWidth = 0, minWidthIntrinsic = 0, maxWidth = 0;
for (int col = 0; col < colExtremes->size(); col++) {
minWidth += colExtremes->getRef(col)->minWidth;
minWidthIntrinsic += colExtremes->getRef(col)->minWidthIntrinsic;
maxWidth += colExtremes->getRef(col)->maxWidth;
}
// CSS 'width' defined and effective?
bool totalWidthSpecified = false;
if (getStyle()->width != core::style::LENGTH_AUTO) {
// Even if 'width' is defined, it may not have a defined value. We try
// this trick (should perhaps be replaced by a cleaner solution):
core::Requisition testReq = { -1, -1, -1 };
correctRequisition (&testReq, core::splitHeightPreserveDescent, true,
false);
if (testReq.width != -1)
totalWidthSpecified = true;
}
DBG_OBJ_MSGF ("resize", 1,
"minWidth = %d, minWidthIntrinsic = %d, maxWidth %d, "
"totalWidth = %d, %s",
minWidth, minWidthIntrinsic, maxWidth, totalWidth,
totalWidthSpecified ? "specified" : "not specified");
if (minWidth > totalWidth) {
DBG_OBJ_MSG ("resize", 1, "case 1: minWidth > totalWidth");
// The sum of all column minima is larger than the available
// width, so we narrow the columns (see also CSS2 spec,
// section 17.5, #6). We use a similar apportioning, but not
// bases on minimal and maximal widths, but on intrinsic minimal
// widths and corrected minimal widths. This way, intrinsic
// extremes are preferred (so avoiding columns too narrow for
// the actual contents), at the expenses of corrected ones
// (which means that sometimes CSS values are handled
// incorrectly).
// A special case is a table with columns whose widths are
// defined by percentage values. In this case, all other columns
// are applied the intrinsic minimal width, while larger values
// are applied to the columns with percentage width (but not
// larger than the corrected width). The left columns are
// preferred, but it is ensured that no column is narrower than
// the intrinsic minimum.
//
// Example two columns with both "width: 70%" will be displayed like
// this:
//
// --------------------------------------------------
// | | |
// --------------------------------------------------
//
// The first gets indeed 70% of the total width, the second only
// the rest.
//
// This somewhat strange behaviour tries to mimic the somewhat
// strange behaviour of Firefox and Chromium.
if (numColWidthPercentage == 0 || minWidthIntrinsic >= totalWidth) {
// Latter case (minWidthIntrinsic >= totalWidth): special treating
// of percentage values would not make sense.
DBG_OBJ_MSG ("resize", 1, "case 1a: simple apportioning");
apportion2 (totalWidth, 0, colExtremes->size() - 1, MIN_MIN, MAX_MIN,
NULL, &colWidths, 0);
} else {
DBG_OBJ_MSG ("resize", 1, "case 1b: treat percentages specially");
// Keep track of the width which is apportioned to the rest
// of the columns with percentage width (widthPartPer), and
// the minimal width (intrinsic minimum) which is needed for
// the rest of these columns (minWidthIntrinsicPer).
int widthPartPer = totalWidth, minWidthIntrinsicPer = 0;
for (int col = 0; col < colExtremes->size(); col++)
if (colWidthPercentage->get (col))
minWidthIntrinsicPer +=
colExtremes->getRef(col)->minWidthIntrinsic;
else
// Columns without percentage width get only the
// intrinsic minimal, so subtract this from the width for the
// columns *with* percentage
widthPartPer -=
colExtremes->getRef(col)->minWidthIntrinsic;
DBG_OBJ_MSGF ("resize", 1,
"widthPartPer = %d, minWidthIntrinsicPer = %d",
widthPartPer, minWidthIntrinsicPer);
for (int col = 0; col < colExtremes->size(); col++)
if (colWidthPercentage->get (col)) {
int colWidth = colExtremes->getRef(col)->minWidth;
int minIntr = colExtremes->getRef(col)->minWidthIntrinsic;
minWidthIntrinsicPer -= minIntr;
if (colWidth > widthPartPer - minWidthIntrinsicPer)
colWidth = widthPartPer - minWidthIntrinsicPer;
colWidths[ col ]= colWidth;
widthPartPer -= colWidth;
DBG_OBJ_MSGF ("resize", 1,
"#%d: colWidth = %d ... widthPartPer = %d, "
"minWidthIntrinsicPer = %d",
col, colWidth, widthPartPer, minWidthIntrinsicPer);
} else
colWidths[ col ]= colExtremes->getRef(col)->minWidthIntrinsic;
}
} else if (totalWidthSpecified && totalWidth > maxWidth) {
DBG_OBJ_MSG ("resize", 1,
"case 2: totalWidthSpecified && totalWidth > maxWidth");
// The width is specified (and so enforced), but all maxima sum
// up to less than this specified width. The columns will have
// there maximal width, and the extra space is apportioned
// according to the column widths, and so to the column
// maxima. This is done by simply passing MAX twice to the
// apportioning function.
// When column widths are specified (numColWidthSpecified > 0,
// as calculated in forceCalcColumnExtremes()), they are treated
// specially and excluded from the apportioning, so that the
// specified column widths are enforced. An exception is when
// all columns are specified: in this case they must be
// enlargened to fill the whole table width.
if (numColWidthSpecified == 0 ||
numColWidthSpecified == colExtremes->size()) {
DBG_OBJ_MSG ("resize", 1,
"subcase 2a: no or all columns with specified width");
apportion2 (totalWidth, 0, colExtremes->size() - 1, MAX, MAX, NULL,
&colWidths, 0);
} else {
DBG_OBJ_MSGF ("resize", 1,
"subcase 2b: %d column(s) with specified width",
numColWidthSpecified);
// Separate columns with specified and unspecified width, and
// apply apportion2() only to the latter.
int numNotSpecified = colExtremes->size() - numColWidthSpecified;
misc::SimpleVector<int> widthsNotSpecified (numNotSpecified);
widthsNotSpecified.setSize (numNotSpecified);
std::vector< int > apportionDest( numNotSpecified );
int totalWidthNotSpecified = totalWidth, indexNotSpecified = 0;
for (int col = 0; col < colExtremes->size(); col++)
if (colWidthSpecified->get (col))
totalWidthNotSpecified -= colExtremes->getRef(col)->maxWidth;
else {
widthsNotSpecified.set (indexNotSpecified,
colExtremes->getRef(col)->maxWidth);
indexNotSpecified++;
}
DBG_IF_RTFL {
DBG_OBJ_MSGF ("resize", 1, "totalWidthNotSpecified = %d",
totalWidthNotSpecified);
DBG_OBJ_MSG ("resize", 1, "widthsNotSpecified:");
DBG_OBJ_MSG_START ();
for (int i = 0; i < widthsNotSpecified.size (); i++)
DBG_OBJ_MSGF ("resize", 1, "#%d: %d",
i, widthsNotSpecified.get (i));
DBG_OBJ_MSG_END ();
}
apportion2 (totalWidthNotSpecified, 0, numNotSpecified - 1, DATA, DATA,
(void*)&widthsNotSpecified, &apportionDest, 0);
DBG_IF_RTFL {
DBG_OBJ_MSG ("resize", 1, "apportionDest:");
DBG_OBJ_MSG_START ();
for (int i = 0; i < apportionDest.size (); i++)
DBG_OBJ_MSGF ("resize", 1, "#%d: %d", i, apportionDest.get (i));
DBG_OBJ_MSG_END ();
}
DBG_OBJ_MSG ("resize", 1, "finally setting column widths:");
DBG_OBJ_MSG_START ();
indexNotSpecified = 0;
for (int col = 0; col < colExtremes->size(); col++)
if (colWidthSpecified->get (col)) {
DBG_OBJ_MSGF ("resize", 1, "#%d: specified, gets maximum %d",
col, colExtremes->getRef(col)->maxWidth);
colWidths[ col ]= colExtremes->getRef(col)->maxWidth;
} else {
DBG_OBJ_MSGF ("resize", 1, "#%d: not specified, gets value %d "
"at position %d from temporary list",
col, apportionDest.at (indexNotSpecified),
indexNotSpecified);
colWidths[ col ]= apportionDest.at (indexNotSpecified);
indexNotSpecified++;
}
DBG_OBJ_MSG_END ();
}
} else {
// Normal apportioning.
int width =
totalWidthSpecified ? totalWidth : std::min (totalWidth, maxWidth);
DBG_OBJ_MSGF ("resize", 1, "case 3: else; width = %d", width);
apportion2 (width, 0, colExtremes->size() - 1, MIN, MAX, NULL, &colWidths,
0);
}
// TODO: Adapted from old inline function "setColWidth". But (i) is
// this anyway correct (col width is is not x)? And does the
// performance gain actually play a role?
for (int col = 0; col < colExtremes->size(); col++) {
if (colWidths.at (col) != oldColWidths.at (col))
redrawX = std::min (redrawX, colWidths.at (col));
}
DBG_IF_RTFL {
DBG_OBJ_SET_NUM ("colWidths.size", colWidths->size ());
for (int i = 0; i < colWidths.size (); i++)
DBG_OBJ_ARRSET_NUM ("colWidths", i, colWidths->get (i));
}
colWidthsUpToDateWidthColExtremes = true;
DBG_OBJ_SET_BOOL ("colWidthsUpToDateWidthColExtremes",
colWidthsUpToDateWidthColExtremes);
for (int col = 0; col < numCols; col++) {
if (col >= oldColWidths.size () || col >= colWidths.size () ||
oldColWidths.at (col) != colWidths.at (col)) {
// Column width has changed, tell children about this.
for (int row = 0; row < numRows; row++) {
int n = row * numCols + col;
// TODO: Columns spanning several rows are only regarded
// when the first column is affected.
if (childDefined (n))
children->get(n)->cell.widget->containerSizeChanged ();
}
}
}
oldColWidths.clear();
oldColWidths.shrink_to_fit();
if (calcHeights) {
setCumHeight (0, 0);
for (int row = 0; row < numRows; row++) {
/**
* \bug dw::Table::baseline is not filled.
*/
int rowHeight = 0;
for (int col = 0; col < numCols; col++) {
int n = row * numCols + col;
if (childDefined (n)) {
/* FIXME: Variable width is not used */
#if 0
int width = (children->get(n)->cell.colspanEff - 1)
* getStyle()->hBorderSpacing;
for (int i = 0; i < children->get(n)->cell.colspanEff; i++)
width += colWidths->get (col + i);
#endif
core::Requisition childRequisition;
//children->get(n)->cell.widget->setWidth (width);
children->get(n)->cell.widget->sizeRequest (&childRequisition);
childHeight = childRequisition.ascent + childRequisition.descent;
if (children->get(n)->cell.rowspan == 1) {
rowHeight = std::max (rowHeight, childHeight);
} else {
rowSpanCells->increase();
rowSpanCells->set(rowSpanCells->size()-1, n);
}
}
} // for col
setCumHeight (row + 1,
cumHeight.at (row) + rowHeight + getStyle()->vBorderSpacing);
} // for row
apportionRowSpan ();
}
DBG_OBJ_LEAVE ();
}
void Table::apportionRowSpan ()
{
DBG_OBJ_ENTER0 ("resize", 0, "apportionRowSpan");
int *rowHeight = NULL;
for (int c = 0; c < rowSpanCells->size(); ++c) {
int n = rowSpanCells->get(c);
int row = n / numCols;
int rs = children->get(n)->cell.rowspan;
int sumRows = cumHeight.at(row+rs) - cumHeight.at(row);
core::Requisition childRequisition;
children->get(n)->cell.widget->sizeRequest (&childRequisition);
int spanHeight = childRequisition.ascent + childRequisition.descent
+ getStyle()->vBorderSpacing;
if (sumRows >= spanHeight)
continue;
// Cell size is too small.
_MSG("Short cell %d, sumRows=%d spanHeight=%d\n",
n,sumRows,spanHeight);
// Fill height array
if (!rowHeight) {
rowHeight = new int[numRows];
for (int i = 0; i < numRows; i++)
rowHeight[i] = cumHeight.at(i+1) - cumHeight.at(i);
}
#ifdef DBG
MSG(" rowHeight { ");
for (int i = 0; i < numRows; i++)
MSG("%d ", rowHeight[i]);
MSG("}\n");
#endif
// Calc new row sizes for this span.
int cumHnew_i = 0, cumh_i = 0, hnew_i;
for (int i = row; i < row + rs; ++i) {
hnew_i =
sumRows == 0 ? (int)((float)(spanHeight-cumHnew_i)/(row+rs-i)) :
(sumRows-cumh_i) <= 0 ? 0 :
(int)((float)(spanHeight-cumHnew_i)*rowHeight[i]/(sumRows-cumh_i));
_MSG(" i=%-3d h=%d hnew_i=%d =%d*%d/%d cumh_i=%d cumHnew_i=%d\n",
i,rowHeight[i],hnew_i,
spanHeight-cumHnew_i,rowHeight[i],sumRows-cumh_i,
cumh_i, cumHnew_i);
cumHnew_i += hnew_i;
cumh_i += rowHeight[i];
rowHeight[i] = hnew_i;
}
// Update cumHeight
for (int i = 0; i < numRows; ++i)
setCumHeight (i+1, cumHeight.at(i) + rowHeight[i]);
}
delete[] rowHeight;
DBG_OBJ_LEAVE ();
}
/**
* \brief Fills dw::Table::colExtremes in all cases.
*/
void Table::forceCalcColumnExtremes ()
{
DBG_OBJ_ENTER0 ("resize", 0, "forceCalcColumnExtremes");
if (numCols > 0) {
lout::misc::SimpleVector<int> colSpanCells (8);
colExtremes->setSize (numCols);
colWidthSpecified->setSize (numCols);
colWidthPercentage->setSize (numCols);
// 1. cells with colspan = 1
for (int col = 0; col < numCols; col++) {
DBG_OBJ_MSGF ("resize", 1, "column %d", col);
DBG_OBJ_MSG_START ();
colWidthSpecified->set (col, false);
colWidthPercentage->set (col, false);
colExtremes->getRef(col)->minWidth = 0;
colExtremes->getRef(col)->minWidthIntrinsic = 0;
colExtremes->getRef(col)->maxWidth = 0;
colExtremes->getRef(col)->maxWidthIntrinsic = 0;
colExtremes->getRef(col)->adjustmentWidth = 0;
for (int row = 0; row < numRows; row++) {
DBG_OBJ_MSGF ("resize", 1, "row %d", row);
DBG_OBJ_MSG_START ();
int n = row * numCols + col;
if (childDefined (n)) {
if (children->get(n)->cell.colspanEff == 1) {
core::Extremes cellExtremes;
children->get(n)->cell.widget->getExtremes (&cellExtremes);
DBG_OBJ_MSGF ("resize", 1, "child: %d / %d",
cellExtremes.minWidth, cellExtremes.maxWidth);
colExtremes->getRef(col)->minWidthIntrinsic =
std::max (colExtremes->getRef(col)->minWidthIntrinsic,
cellExtremes.minWidthIntrinsic);
colExtremes->getRef(col)->maxWidthIntrinsic =
std::max( colExtremes->getRef(col)->minWidthIntrinsic,
std::max( colExtremes->getRef(col)->maxWidthIntrinsic,
cellExtremes.maxWidthIntrinsic ) );
colExtremes->getRef(col)->minWidth =
std::max (colExtremes->getRef(col)->minWidth,
cellExtremes.minWidth);
colExtremes->getRef(col)->maxWidth =
std::max( colExtremes->getRef(col)->minWidth,
std::max( colExtremes->getRef(col)->maxWidth,
cellExtremes.maxWidth ) );
colExtremes->getRef(col)->adjustmentWidth =
std::max (colExtremes->getRef(col)->adjustmentWidth,
cellExtremes.adjustmentWidth);
core::style::Length childWidth =
children->get(n)->cell.widget->getStyle()->width;
if (childWidth != core::style::LENGTH_AUTO) {
colWidthSpecified->set (col, true);
if (core::style::isPerLength (childWidth))
colWidthPercentage->set (col, true);
}
DBG_OBJ_MSGF ("resize", 1, "column: %d / %d (%d / %d)",
colExtremes->getRef(col)->minWidth,
colExtremes->getRef(col)->maxWidth,
colExtremes->getRef(col)->minWidthIntrinsic,
colExtremes->getRef(col)->maxWidthIntrinsic);
} else {
colSpanCells.increase ();
colSpanCells.setLast (n);
}
}
DBG_OBJ_MSG_END ();
}
DBG_OBJ_MSG_END ();
}
// 2. cells with colspan > 1
// TODO: Is this old comment still relevant? "If needed, here we
// set proportionally apportioned col maximums."
for (int i = 0; i < colSpanCells.size(); i++) {
int n = colSpanCells.get (i);
int col = n % numCols;
int cs = children->get(n)->cell.colspanEff;
core::Extremes cellExtremes;
children->get(n)->cell.widget->getExtremes (&cellExtremes);
calcExtremesSpanMultiCols (col, cs, &cellExtremes, MIN, MAX, NULL);
calcExtremesSpanMultiCols (col, cs, &cellExtremes, MIN_INTR, MAX_INTR,
NULL);
calcAdjustmentWidthSpanMultiCols (col, cs, &cellExtremes);
core::style::Length childWidth =
children->get(n)->cell.widget->getStyle()->width;
if (childWidth != core::style::LENGTH_AUTO) {
for (int j = 0; j < cs; j++)
colWidthSpecified->set (col + j, true);
if (core::style::isPerLength (childWidth))
for (int j = 0; j < cs; j++)
colWidthPercentage->set (col + j, true);
}
}
}
numColWidthSpecified = 0;
numColWidthSpecified = 0;
for (int i = 0; i < colExtremes->size (); i++) {
if (colWidthSpecified->get (i))
numColWidthSpecified++;
if (colWidthPercentage->get (i))
numColWidthPercentage++;
}
DBG_IF_RTFL {
DBG_OBJ_SET_NUM ("colExtremes.size", colExtremes->size ());
for (int i = 0; i < colExtremes->size (); i++) {
DBG_OBJ_ARRATTRSET_NUM ("colExtremes", i, "minWidth",
colExtremes->get(i).minWidth);
DBG_OBJ_ARRATTRSET_NUM ("colExtremes", i, "minWidthIntrinsic",
colExtremes->get(i).minWidthIntrinsic);
DBG_OBJ_ARRATTRSET_NUM ("colExtremes", i, "maxWidth",
colExtremes->get(i).maxWidth);
DBG_OBJ_ARRATTRSET_NUM ("colExtremes", i, "maxWidthIntrinsic",
colExtremes->get(i).maxWidthIntrinsic);
}
DBG_OBJ_SET_NUM ("colWidthSpecified.size", colWidthSpecified->size ());
for (int i = 0; i < colWidthSpecified->size (); i++)
DBG_OBJ_ARRSET_BOOL ("colWidthSpecified", i,
colWidthSpecified->get(i));
DBG_OBJ_SET_NUM ("numColWidthSpecified", numColWidthSpecified);
DBG_OBJ_SET_NUM ("colWidthPercentage.size", colWidthPercentage->size ());
for (int i = 0; i < colWidthPercentage->size (); i++)
DBG_OBJ_ARRSET_BOOL ("colWidthPercentage", i,
colWidthPercentage->get(i));
DBG_OBJ_SET_NUM ("numColWidthPercentage", numColWidthPercentage);
}
colWidthsUpToDateWidthColExtremes = false;
DBG_OBJ_SET_BOOL ("colWidthsUpToDateWidthColExtremes",
colWidthsUpToDateWidthColExtremes);
DBG_OBJ_LEAVE ();
}
void Table::calcExtremesSpanMultiCols (int col, int cs,
core::Extremes *cellExtremes,
ExtrMod minExtrMod, ExtrMod maxExtrMod,
void *extrData)
{
DBG_OBJ_ENTER ("resize", 0, "calcExtremesSpanMulteCols",
"%d, %d, ..., %s, %s, ...",
col, cs, getExtrModName (minExtrMod),
getExtrModName (maxExtrMod));
int cellMin = getExtreme (cellExtremes, minExtrMod);
int cellMax = getExtreme (cellExtremes, maxExtrMod);
int minSumCols = 0, maxSumCols = 0;
for (int j = 0; j < cs; j++) {
minSumCols += getColExtreme (col + j, minExtrMod, extrData);
maxSumCols += getColExtreme (col + j, maxExtrMod, extrData);
}
DBG_OBJ_MSGF ("resize", 1, "cs = %d, cell: %d / %d, sum: %d / %d\n",
cs, cellMin, cellMax, minSumCols, maxSumCols);
bool changeMin = cellMin > minSumCols;
bool changeMax = cellMax > maxSumCols;
if (changeMin || changeMax) {
// TODO This differs from the documentation? Should work, anyway.
std::vector< int > newMin;
std::vector< int > newMax;
if (changeMin)
apportion2 (cellMin, col, col + cs - 1, MIN, MAX, NULL, &newMin, 0);
if (changeMax)
apportion2 (cellMax, col, col + cs - 1, MIN, MAX, NULL, &newMax, 0);
for (int j = 0; j < cs; j++) {
if (changeMin)
setColExtreme (col + j, minExtrMod, extrData, newMin.at (j));
if (changeMax)
setColExtreme (col + j, maxExtrMod, extrData, newMax.at (j));
// For cases where min and max are somewhat confused:
setColExtreme (col + j, maxExtrMod, extrData,
std::max (getColExtreme (col + j, minExtrMod,
extrData),
getColExtreme (col + j, maxExtrMod,
extrData)));
}
}
DBG_OBJ_LEAVE ();
}
void Table::calcAdjustmentWidthSpanMultiCols (int col, int cs,
core::Extremes *cellExtremes)
{
DBG_OBJ_ENTER ("resize", 0, "calcAdjustmentWidthSpanMultiCols",
"%d, %d, ...", col, cs);
int sumAdjustmentWidth = 0;
for (int j = 0; j < cs; j++)
sumAdjustmentWidth += colExtremes->getRef(col + j)->adjustmentWidth;
if (cellExtremes->adjustmentWidth > sumAdjustmentWidth) {
std::vector< int > newAdjustmentWidth;
apportion2 (cellExtremes->adjustmentWidth, col, col + cs - 1, MIN, MAX,
NULL, &newAdjustmentWidth, 0);
for (int j = 0; j < cs; j++)
colExtremes->getRef(col + j)->adjustmentWidth =
newAdjustmentWidth.at (j);
}
DBG_OBJ_LEAVE ();
}
/**
* \brief Actual apportionment function.
*/
void Table::apportion2 (int totalWidth, int firstCol, int lastCol,
ExtrMod minExtrMod, ExtrMod maxExtrMod, void *extrData,
std::vector<int> *dest, int destOffset)
{
DBG_OBJ_ENTER ("resize", 0, "apportion2", "%d, %d, %d, %s, %s, ..., %d",
totalWidth, firstCol, lastCol, getExtrModName (minExtrMod),
getExtrModName (maxExtrMod), destOffset);
if (lastCol >= firstCol) {
dest->resize( destOffset + lastCol - firstCol + 1, 0 );
int totalMin = 0, totalMax = 0;
for (int col = firstCol; col <= lastCol; col++) {
totalMin += getColExtreme (col, minExtrMod, extrData);
totalMax += getColExtreme (col, maxExtrMod, extrData);
}
DBG_OBJ_MSGF ("resize", 1,
"totalWidth = %d, totalMin = %d, totalMax = %d",
totalWidth, totalMin, totalMax);
// The actual calculation is rather simple, the ith value is:
//
//
// (max[i] - min[i]) * (totalMax - totalMin)
// width[i] = min[i] + -----------------------------------------
// (totalWidth - totalMin)
//
// (Regard "total" as "sum".) With the following general
// definitions (for both the list and sums):
//
// diffExtr = max - min
// diffWidth = width - min
//
// it is simplified to:
//
// diffExtr[i] * totalDiffWidth
// diffWidth[i] = ----------------------------
// totalDiffExtr
//
// Of course, if totalDiffExtr is 0, this is not defined;
// instead, we apportion according to the minima:
//
// min[i] * totalWidth
// width[i] = -------------------
// totalMin
//
// Since min[i] <= max[i] for all i, totalMin == totalMax
// implies that min[i] == max[i] for all i.
//
// Third, it totalMin == 0 (which also implies min[i] = max[i] = 0),
// the result is
//
// width[i] = totalWidth / n
int totalDiffExtr = totalMax - totalMin;
if (totalDiffExtr != 0) {
// Normal case. The algorithm described in
// "rounding-errors.doc" is used, with:
//
// x[i] = diffExtr[i]
// y[i] = diffWidth[i]
// a = totalDiffWidth
// b = totalDiffExtr
DBG_OBJ_MSG ("resize", 1, "normal case");
int totalDiffWidth = totalWidth - totalMin;
int cumDiffExtr = 0, cumDiffWidth = 0;
for (int col = firstCol; col <= lastCol; col++) {
int min = getColExtreme (col, minExtrMod, extrData);
int max = getColExtreme (col, maxExtrMod, extrData);
int diffExtr = max - min;
cumDiffExtr += diffExtr;
int diffWidth =
(cumDiffExtr * totalDiffWidth) / totalDiffExtr - cumDiffWidth;
cumDiffWidth += diffWidth;
dest->at( destOffset - firstCol + col )= diffWidth + min;
}
} else if (totalMin != 0) {
// Special case. Again, same algorithm, with
//
// x[i] = min[i]
// y[i] = width[i]
// a = totalWidth
// b = totalMin
DBG_OBJ_MSG ("resize", 1, "special case 1");
int cumMin = 0, cumWidth = 0;
for (int col = firstCol; col <= lastCol; col++) {
int min = getColExtreme (col, minExtrMod, extrData);
cumMin += min;
int width = (cumMin * totalWidth) / totalMin - cumWidth;
cumWidth += width;
dest->at( destOffset - firstCol + col )= width;
}
} else { // if (totalMin == 0)
// Last special case. Same algorithm, with
//
// x[i] = 1 (so cumX = i = col - firstCol + 1)
// y[i] = width[i]
// a = totalWidth
// b = n = lastCol - firstCol + 1
DBG_OBJ_MSG ("resize", 1, "special case 2");
int cumWidth = 0, n = (lastCol - firstCol + 1);
for (int col = firstCol; col <= lastCol; col++) {
int i = (col - firstCol + 1);
int width = (i * totalWidth) / n - cumWidth;
cumWidth += width;
dest->at( destOffset - firstCol + col )= width;
}
}
}
DBG_OBJ_LEAVE ();
}
} // namespace dw