diff options
Diffstat (limited to 'dw/table.hh')
-rw-r--r-- | dw/table.hh | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/dw/table.hh b/dw/table.hh new file mode 100644 index 00000000..ec2bacc8 --- /dev/null +++ b/dw/table.hh @@ -0,0 +1,486 @@ +#ifndef __DW_TABLE_HH__ +#define __DW_TABLE_HH__ + +#include "core.hh" +#include "tablecell.hh" +#include "../lout/misc.hh" + +namespace dw { + +/** + * \brief A Widget for rendering tables. + * + * <h3>Introduction</h3> + * + * The dw::Table widget is used to render HTML tables. + * + * Each cell is itself an own widget. Any widget may be used, however, in + * dillo, only instances of dw::Textblock and dw::TableCell are used as + * children of dw::Table. + * + * + * <h3>Sizes</h3> + * + * <h4>General</h4> + * + * The following diagram shows the dependencies between the different + * functions, which are related to size calculation. Click on the boxes + * for more informations. + * + * \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"]; + * getExtremesImpl [color="#0000ff", URL="\ref dw::Table::getExtremesImpl"]; + * + * subgraph cluster_sizes { + * style="dashed"; color="#8080c0"; + * calcCellSizes [URL="\ref dw::Table::calcCellSizes"]; + * forceCalcCellSizes [URL="\ref dw::Table::forceCalcCellSizes"]; + * } + * + * subgraph cluster_extremes { + * style="dashed"; color="#8080c0"; + * calcColumnExtremes [URL="\ref dw::Table::calcColumnExtremes"]; + * forceCalcColumnExtremes[URL="\ref dw::Table::forceCalcColumnExtremes"]; + * } + * + * sizeRequestImpl -> forceCalcCellSizes [label="[B]"]; + * sizeAllocateImpl -> calcCellSizes [label="[A]"]; + * getExtremesImpl -> forceCalcColumnExtremes [label="[B]"]; + * + * forceCalcCellSizes -> calcColumnExtremes; + * + * calcCellSizes -> forceCalcCellSizes [style="dashed", label="[C]"]; + * calcColumnExtremes -> forceCalcColumnExtremes [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 / + * EXTREMES_CHANGED. + * + * + * <h4>Apportionment</h4> + * + * \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 independant 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. + * + * <h4>Column Extremes</h4> + * + * \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: + * + * <ol> + * <li> 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. + * + * <li> 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 exeeds 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: + * + * <ul> + * <li> If the minimal width of this cell also exeeds the sum of the + * column maxima: + * + * \f[e_{\hbox{cell},i_1,j,\min} > + * \sum_{i=i_1}^{i=i_2} e_{\hbox{base},i,\max}\f] + * + * then \f$e_{\hbox{cell},i_1,j,\min}\f$ is apportioned in a simple + * way: + * + * \f[e_{\hbox{span},i,j,\min} = + * e_{\hbox{base},i,\max} + * {e_{\hbox{span},i,j,\min} \over + * \sum_{i=i_1}^{i=i_2} e_{\hbox{base},i,\max}}\f] + * + * <li> Otherwise, the apportionment function is used: + * + * \f[e_{\hbox{span},i,j,\min} = + * a_i (e_{\hbox{cell},i_1,j,\min}, + * (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] + * </ul> + * + * After this, \f$e_{\hbox{span},i,\min}\f$ is then the maximum of all + * \f$e_{\hbox{span},i,j,\min}\f$. + * + * <li> 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 or equal than/to the + * minimum. + * </ol> + * + * Generally, if absolute widths are speficied, 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] + * + * <h4>Cell Sizes</h4> + * + * <h5>Determining the Width of the Table</h5> + * + * The total width is + * + * <ul> + * <li> the specified absolute width of the table, when given, or + * <li> the available width (set by dw::Table::setWidth) times the specifies + * percentage width pf t(at max 100%), if the latter is given, or + * <li> otherwise the available width. + * </ul> + * + * 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 parantheses 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$. + * + * <h5>Evaluating percentages</h5> + * + * The following algorithms are used to solve collisions between + * different size specifications (absolute and percentage). Generally, + * inherent sizes and specified absolute sizes are preferred. + * + * <ol> + * <li> 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. + * + * <li> Then, calculate the sum of the widths, which the columns with + * percentage width specification would allocate, when fully adhering to + * then: + * + * \f[W_{\hbox{columns}_\%,\hbox{best}} = W \sum w_{i,\%}\f] + * + * with only those columns \f$i\f$ with a percentage width specification. + * + * <li> Two cases are distinguished: + * + * <ul> + * <li> \f$W_{\hbox{columns}_\%,\hbox{available}} \ge + * W_{\hbox{columns}_\%,\hbox{best}}\f$: In this case, the + * percentage widths can be used without any modification, by + * setting the extremes: + * + * \f[e_{i,\min} = e_{i,\max} = W w_{i,\%}\f] + * + * for only those columns \f$i\f$ with a percentage width + * specification. + * + * <li> \f$W_{\hbox{columns}_\%,\hbox{available}} < + * W_{\hbox{columns}_\%,\hbox{best}}\f$: In this case, the widths + * for these columns must be cut down: + * + * \f[e_{i,\min} = e_{i,\max} = + * w_{i,\%} + * {W_{\hbox{columns}_\%,\hbox{available}} \over + * w_{\hbox{total},\%}}\f] + * + * with + * + * \f[w_{\hbox{total},\%} = \sum w_{i,\%}\f] + * + * in both cases for only those columns \f$i\f$ with a percentage + * width specification. + * </ul> + * </ol> + * + * (\f$e_{i,\min}\f$ and \f$e_{i,\max}\f$ are set \em temporarily here, + * the notation should be a bit clearer.) + * + * + * <h5>Column Widths</h5> + * + * The column widths are now simply calculated by applying the + * apportenment function. + * + * + * <h5>Row Heights</h5> + * + * ... + * + * <h3>Alternative Apportionment Algorithm</h3> + * + * The algorithm described here tends to result in more homogeneous column + * widths. + * + * The following rule lead to well-defined \f$w_{i}\f$: All columns + * \f$i\f$ have have the same width \f$w\f$, except: + * <ul> + * <li> \f$w < e_{i,\min}\f$, or + * <li> \f$w > e_{i,\max}\f$. + * </ul> + * + * Furthermore, \f$w\f$ is + * <ul> + * <li> less than all \f$e_{i,\min}\f$ of columns not having \f$w\f$ as + * width, and + * <li> greater than all \f$e_{i,\min}\f$ of columns not having \f$w\f$ as + * width. + * </ul> + * + * 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. + * + * + * <h3>Borders, Paddings, Spacing</h3> + * + * 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: + * + * <pre> + * 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; + * } + * </pre> + * + * 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 core::Widget +{ +private: + + struct Child + { + enum { + CELL, // cell starts here + SPAN_SPACE // part of a spanning cell + } type; + union { + struct { + core::Widget *widget; + int colspanOrig, colspanEff, rowspan; + } cell; + struct { + int startCol, startRow; // where the cell starts + } spanSpace; + }; + }; + + class TableIterator: public core::Iterator + { + private: + int index; + + public: + TableIterator (Table *table, core::Content::Type mask, bool atEnd); + TableIterator (Table *table, core::Content::Type mask, int index); + + object::Object *clone(); + int compareTo(misc::Comparable *other); + + bool next (); + bool prev (); + 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; + + bool limitTextWidth, rowClosed; + int availWidth, availAscent, availDescent; // set by set... + + int numRows, numCols, curRow, curCol; + misc::SimpleVector<Child*> *children; + + int redrawX, redrawY; + + /** + * \brief The extremes of all columns. + */ + misc::SimpleVector<core::Extremes> *colExtremes; + + /** + * \brief The widths of all columns. + */ + misc::SimpleVector<int> *colWidths; + + /** + * Row cumulative height array: cumHeight->size() is numRows + 1, + * cumHeight->get(0) is 0, cumHeight->get(numRows) is the total table + * height. + */ + misc::SimpleVector<int> *cumHeight; + /** + * If a Cell has rowspan > 1, it goes into this array + */ + misc::SimpleVector<int> *rowSpanCells; + /** + * If a Cell has colspan > 1, it goes into this array + */ + misc::SimpleVector<int> *colSpanCells; + misc::SimpleVector<int> *baseline; + + misc::SimpleVector<core::style::Style*> *rowStyle; + + /** + * hasColPercent becomes true when any cell specifies a percentage width. + * A negative value in colPercents means LEN_AUTO or LEN_ABS. + */ + enum { LEN_AUTO = -1, LEN_ABS = -2}; + int hasColPercent; + misc::SimpleVector<float> *colPercents; + + inline bool childDefined(int n) + { + return n < children->size() && children->get(n) != NULL && + children->get(n)->type != Child::SPAN_SPACE; + } + + void reallocChildren (int newNumCols, int newNumRows); + + void calcCellSizes (); + void forceCalcCellSizes (); + void apportionRowSpan (); + + void calcColumnExtremes (); + void forceCalcColumnExtremes (); + + void apportion2 (int totalWidth, int forceTotalWidth); + void apportion_percentages2 (int totalWidth, int forceTotalWidth); + + void setCumHeight (int row, int value) + { + if (value != cumHeight->get (row)) { + redrawY = misc::min ( redrawY, value ); + cumHeight->set (row, value); + } + } + + inline void setColWidth (int col, int value) + { + if (value != colWidths->get (col)) { + redrawX = misc::min (redrawX, value); + colWidths->set (col, value); + } + } + +protected: + void sizeRequestImpl (core::Requisition *requisition); + void getExtremesImpl (core::Extremes *extremes); + void sizeAllocateImpl (core::Allocation *allocation); + void resizeDrawImpl (); + + void setWidth (int width); + void setAscent (int ascent); + void setDescent (int descent); + void draw (core::View *view, core::Rectangle *area); + + //bool buttonPressImpl (core::EventButton *event); + //bool buttonReleaseImpl (core::EventButton *event); + //bool motionNotifyImpl (core::EventMotion *event); + + void removeChild (Widget *child); + +public: + static int CLASS_ID; + + Table(bool limitTextWidth); + ~Table(); + + core::Iterator *iterator (core::Content::Type mask, bool atEnd); + + void addCell (Widget *widget, int colspan, int rowspan); + void addRow (core::style::Style *style); + TableCell *getCellRef (); +}; + +} // namespace dw + +#endif // __DW_TABLE_HH__ |