#ifndef __DW_TABLE_HH__ #define __DW_TABLE_HH__ #include "oofawarewidget.hh" #include "alignedtablecell.hh" #include "../lout/misc.hh" namespace dw { /** * \brief A Widget for rendering tables. * *
Warning: Some parts of this * description are outdated since \ref dw-grows.
* *

Introduction

* * The dw::Table widget is used to render HTML tables. * * Each cell is itself a separate widget. Any widget may be used, however, in * dillo, only instances of dw::Textblock and dw::TableCell are used as * children of dw::Table. * * *

Sizes

* *

General

* * The following diagram shows the dependencies between the different * functions, which are related to size calculation. Click on the boxes * for more information. * * \dot * digraph G { * node [shape=record, fontname=Helvetica, fontsize=10, color="#c0c0c0"]; * edge [arrowhead="open", arrowtail="none", labelfontname=Helvetica, * labelfontsize=10, color="#404040", labelfontcolor="#000080", * fontname=Helvetica, fontsize=10]; * fontname=Helvetica; fontsize=10; * * sizeRequestImpl [color="#0000ff", URL="\ref dw::Table::sizeRequestImpl"]; * sizeAllocateImpl [color="#0000ff", * URL="\ref dw::Table::sizeAllocateImpl"]; * getExtremes [color="#0000ff", URL="\ref dw::core::Widget::getExtremes"]; * getExtremesImpl [color="#0000ff", URL="\ref dw::Table::getExtremesImpl"]; * * calcCellSizes [label="calcCellSizes (calcHeights = true)", * URL="\ref dw::Table::calcCellSizes"]; * forceCalcCellSizes [label="forceCalcCellSizes (calcHeights = true)", * URL="\ref dw::Table::forceCalcCellSizes"]; * actuallyCalcCellSizes[label="actuallyCalcCellSizes (calcHeights = true)", * URL="\ref dw::Table::actuallyCalcCellSizes"]; * forceCalcColumnExtremes[URL="\ref dw::Table::forceCalcColumnExtremes"]; * * sizeRequestImpl -> forceCalcCellSizes [label="[B]"]; * sizeAllocateImpl -> calcCellSizes [label="[A]"]; * getExtremesImpl -> forceCalcColumnExtremes [label="[B]"]; * * forceCalcCellSizes -> actuallyCalcCellSizes; * actuallyCalcCellSizes-> getExtremes; * getExtremes -> getExtremesImpl [style="dashed", label="[C]"]; * * calcCellSizes -> forceCalcCellSizes [style="dashed", label="[C]"]; * } * \enddot * * [A] In this case, the new calculation is \em not forced, but only * done, when necessary. * * [B] In this case, the new calculation is allways necessary, since [C] * is the case. * * [C] Whether this function is called, depends on NEEDS_RESIZE / * RESIZE_QUEUED / EXTREMES_CHANGED / EXTREMES_QUEUED. * * **TODO:** * * - Are *[cC]alcCellSizes (calcHeights = *false*) not * necessary anymore? * - Calculating available sizes (Table::getAvailWidthOfChild) should * be documented in this diagram, too. * *

Apportionment

* * \sa\ref rounding-errors * * Given two array \f$e_{i,\min}\f$ and \f$e_{i,\max}\f$, which * represent the column minima and maxima, and a total width \f$W\f$, \em * apportionment means to calculate column widths \f$w_{i}\f$, with * * \f[e_{i,\min} \le w_{i} \le e_{i,\max}\f] * * and * * \f[\sum w_{i} = W\f] * * There are different algorithms for apportionment, a simple one is * recommended in the HTML 4.0.1 specification * (http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.5.2.2): * * \f[w_{i} = e_{i,\min} + * {e_{i,\max} - e_{i,\min}\over\sum e_{i,\max} - \sum e_{i,\min}} * (W - \sum e_{i,\min})\f] * * This one is used currently, but another one will be used soon, which is * described below. The rest of this chapter is independent of the exact * apportionment algorithm. * * When referring to the apportionment function, we will call it * \f$a_i (W, (e_{i,\min}), (e_{i,\min}))\f$ and write * something like this: * * \f[w_{i} = a_i (W, (e_{i,\min}), (e_{i,\max})) \f] * * It is implemented by dw::Table::apportion. * *

Column Extremes

* * \sa\ref rounding-errors * * The sizes, which all other sizes depend on, are column extremes, which * define, how wide a column may be at min and at max. They are * calculated in the following way: * *
    *
  1. First, only cells with colspan = 1 are regarded: * \f[ e_{\hbox{base},i,\min} = \max \{ e_{\hbox{cell},i,j,\min} \} \f] * \f[ e_{\hbox{base},i,\max} = \max \{ e_{\hbox{cell},i,j,\max} \} \f] * only for cells \f$(i, j)\f$ with colspan = 1. * *
  2. Then, * \f$e_{\hbox{span},i,\min}\f$ (but not \f$e_{\hbox{span},i,\max}\f$) * are calculated from cells with colspan > 1. (In the following formulas, * the cell at \f$(i_1, j)\f$ always span from \f$i_1\f$ to \f$i_2\f$.) * If the minimal width of the column exceeds the sum of the column minima * calculated in the last step: * \f[e_{\hbox{cell},i_1,j,\min} > * \sum_{i=i_1}^{i=i_2} e_{\hbox{base},i,\min}\f] * then the minimal width of this cell is apportioned to the columns: * * * * After this, \f$e_{\hbox{span},i,\min}\f$ is then the maximum of all * \f$e_{\hbox{span},i,j,\min}\f$. * *
  3. Finally, the maximum of both is used. * \f[ e_{i,\min} = * \max \{ e_{\hbox{base},i,\min}, e_{\hbox{span},i,\min} \} \f] * \f[ e_{i,\max} = * \max \{ e_{\hbox{base},i,\max}, e_{i,\min} \} \f] * For the maxima, there is no \f$e_{\hbox{span},i,\max}\f$, but it has to * be assured, that the maximum is always greater than or equal to the * minimum. * *
* * Generally, if absolute widths are specified, they are, instead of the * results of dw::core::Widget::getExtremes, taken for the minimal and * maximal width of a cell (minus the box difference, i.e. the difference * between content size and widget size). If the content width * specification is smaller than the minimal content width of the widget * (determined by dw::core::Widget::getExtremes), the latter is used * instead. * * If percentage widths are specified, they are also collected, as column * maxima. A similar method as for the extremes is used, for cells with * colspan > 1: * * \f[w_{\hbox{span},i,j,\%} = * a_i (w_{\hbox{cell},i_1,j,\%}, * (e_{\hbox{cell},i_1,j,\min} \ldots e_{\hbox{cell},i_2,j,\min}), * (e_{\hbox{cell},i_1,j,\max} \ldots e_{\hbox{cell},i_2,j,\max}))\f] * *

Cell Sizes

* *
Determining the Width of the Table
* * The total width is * * * * In any case, it is corrected, if it is less than the minimal width * (but not if it is greater than the maximal width). * * \bug The parentheses is not fully clear, look at the old code. * * Details on differences because of styles are omitted. Below, this * total width is called \f$W\f$. * *
Evaluating percentages
* * The following algorithms are used to solve collisions between * different size specifications (absolute and percentage). Generally, * inherent sizes and specified absolute sizes are preferred. * *
    *
  1. First, calculate the sum of the minimal widths, for columns, where * no percentage width has been specified. The difference to the total * width is at max available to the columns with percentage width * specifications: * \f[W_{\hbox{columns}_\%,\hbox{available}} = W - \sum e_{i,\min}\f] * with only those columns \f$i\f$ with no percentage width specification. * *
  2. Then, calculate the sum of the widths, which the columns with * percentage width specification would allocate, when fully adhering to * them: * \f[W_{\hbox{columns}_\%,\hbox{best}} = W \sum w_{i,\%}\f] * with only those columns \f$i\f$ with a percentage width specification. * *
  3. Two cases are distinguished: * * *
* * (\f$e_{i,\min}\f$ and \f$e_{i,\max}\f$ are set \em temporarily here, * the notation should be a bit clearer.) * * *
Column Widths
* * The column widths are now simply calculated by applying the * apportionment function. * * *
Row Heights
* * ... * *

Alternative Apportionment Algorithm

* * The algorithm described here tends to result in more homogeneous column * widths. * * The following rule leads to well-defined \f$w_{i}\f$: All columns * \f$i\f$ have have the same width \f$w\f$, except: * * * Furthermore, \f$w\f$ is * * * Of course, \f$\sum w_{i} = W\f$ must be the case. * * Based on an initial value \f$w = {W\over n}\f$, \f$w\f$ can iteratively * adjusted, based on these rules. * * *

Borders, Paddings, Spacing

* * Currently, DwTable supports only the separated borders model (see CSS * specification). Borders, paddings, spacing is done by creating * dw::core::style::Style structures with values equivalent to following CSS: * *
 * TABLE {
 *   border:           outset \em table-border;
 *   border-collapse:  separate;
 *   border-spacing:   \em table-cellspacing;
 *   background-color: \em table-bgcolor;
 * }
 *
 * TD TH {
 *   border:           inset \em table-border;
 *   padding:          \em table-cellspacing;
 *   background-color: \em td/th-bgcolor;
 * }
 * 
* * Here, \em foo-bar refers to the attribute \em bar of the tag \em foo foo. * Look at the HTML parser for more details. */ class Table: public oof::OOFAwareWidget { private: struct Cell { core::Widget *widget; int colspanOrig, colspanEff, rowspan; }; struct SpanSpace { int startCol, startRow; // where the cell starts }; struct Child { enum { CELL, // cell starts here SPAN_SPACE // part of a spanning cell } type; union { struct Cell cell; struct SpanSpace spanSpace; }; }; class TableIterator: public OOFAwareWidgetIterator { protected: int numContentsInFlow (); void getContentInFlow (int index, core::Content *content); public: TableIterator (Table *table, core::Content::Type mask, bool atEnd); lout::object::Object *clone(); void highlight (int start, int end, core::HighlightLayer layer); void unhighlight (int direction, core::HighlightLayer layer); void getAllocation (int start, int end, core::Allocation *allocation); }; friend class TableIterator; static bool adjustTableMinWidth; bool limitTextWidth, rowClosed; int numRows, numCols, curRow, curCol; lout::misc::SimpleVector *children; int redrawX, redrawY; /** * \brief The extremes of all columns. */ lout::misc::SimpleVector *colExtremes; /** * \brief Wether the column itself (in the future?) or at least one * cell in this column or spanning over this column has CSS * 'width' specified. * * Filled by forceCalcColumnExtremes(), since it is needed to * calculate the column widths. */ lout::misc::SimpleVector *colWidthSpecified; int numColWidthSpecified; /** * \brief Wether the column itself (in the future?) or at least one * cell in this column or spanning over this column has CSS * 'width' specified *as percentage value*. * * Filled by forceCalcColumnExtremes(), since it is needed to * calculate the column widths. */ lout::misc::SimpleVector *colWidthPercentage; int numColWidthPercentage; /** * \brief The widths of all columns. */ lout::misc::SimpleVector *colWidths; /** * Row cumulative height array: cumHeight->size() is numRows + 1, * cumHeight->get(0) is 0, cumHeight->get(numRows) is the total table * height. */ lout::misc::SimpleVector *cumHeight; /** * If a Cell has rowspan > 1, it goes into this array */ lout::misc::SimpleVector *rowSpanCells; lout::misc::SimpleVector *baseline; lout::misc::SimpleVector *rowStyle; bool colWidthsUpToDateWidthColExtremes; enum ExtrMod { MIN, MIN_INTR, MIN_MIN, MAX_MIN, MAX, MAX_INTR, DATA }; const char *getExtrModName (ExtrMod mod); int getExtreme (core::Extremes *extremes, ExtrMod mod); void setExtreme (core::Extremes *extremes, ExtrMod mod, int value); int getColExtreme (int col, ExtrMod mod, void *data); inline void setColExtreme (int col, ExtrMod mod, void *data, int value); inline bool childDefined(int n) { return n < children->size() && children->get(n) != NULL && children->get(n)->type != Child::SPAN_SPACE; } int calcAvailWidthForDescendant (Widget *child); void reallocChildren (int newNumCols, int newNumRows); void calcCellSizes (bool calcHeights); void forceCalcCellSizes (bool calcHeights); void actuallyCalcCellSizes (bool calcHeights); void apportionRowSpan (); void forceCalcColumnExtremes (); void calcExtremesSpanMultiCols (int col, int cs, core::Extremes *cellExtremes, ExtrMod minExtrMod, ExtrMod maxExtrMod, void *extrData); void calcAdjustmentWidthSpanMultiCols (int col, int cs, core::Extremes *cellExtremes); void apportion2 (int totalWidth, int firstCol, int lastCol, ExtrMod minExtrMod, ExtrMod maxExtrMod, void *extrData, lout::misc::SimpleVector *dest, int destOffset); void setCumHeight (int row, int value) { if (value != cumHeight->get (row)) { redrawY = lout::misc::min ( redrawY, value ); cumHeight->set (row, value); } } protected: void sizeRequestSimpl (core::Requisition *requisition); void getExtremesSimpl (core::Extremes *extremes); void sizeAllocateImpl (core::Allocation *allocation); void resizeDrawImpl (); bool getAdjustMinWidth () { return Table::adjustTableMinWidth; } int getAvailWidthOfChild (Widget *child, bool forceValue); void containerSizeChangedForChildren (); bool affectsSizeChangeContainerChild (Widget *child); bool usesAvailWidth (); bool isBlockLevel (); void drawLevel (core::View *view, core::Rectangle *area, int level, core::DrawingContext *context); Widget *getWidgetAtPointLevel (int x, int y, int level, core::GettingWidgetAtPointContext *context); //bool buttonPressImpl (core::EventButton *event); //bool buttonReleaseImpl (core::EventButton *event); //bool motionNotifyImpl (core::EventMotion *event); void removeChild (Widget *child); public: static int CLASS_ID; inline static void setAdjustTableMinWidth (bool adjustTableMinWidth) { Table::adjustTableMinWidth = adjustTableMinWidth; } inline static bool getAdjustTableMinWidth () { return Table::adjustTableMinWidth; } Table(bool limitTextWidth); ~Table(); int applyPerWidth (int containerWidth, core::style::Length perWidth); int applyPerHeight (int containerHeight, core::style::Length perHeight); core::Iterator *iterator (core::Content::Type mask, bool atEnd); void addCell (Widget *widget, int colspan, int rowspan); void addRow (core::style::Style *style); AlignedTableCell *getCellRef (); }; } // namespace dw #endif // __DW_TABLE_HH__