diff options
Diffstat (limited to 'javax/swing/text/html/TableView.java')
-rw-r--r-- | javax/swing/text/html/TableView.java | 616 |
1 files changed, 563 insertions, 53 deletions
diff --git a/javax/swing/text/html/TableView.java b/javax/swing/text/html/TableView.java index c2edc8cdd..2bd11ffcf 100644 --- a/javax/swing/text/html/TableView.java +++ b/javax/swing/text/html/TableView.java @@ -38,49 +38,185 @@ exception statement from your version. */ package javax.swing.text.html; -import javax.swing.text.Document; +import gnu.javax.swing.text.html.css.Length; + +import javax.swing.SizeRequirements; +import javax.swing.text.AttributeSet; +import javax.swing.text.BoxView; import javax.swing.text.Element; +import javax.swing.text.StyleConstants; import javax.swing.text.View; import javax.swing.text.ViewFactory; /** - * A conrete implementation of TableView that renders HTML tables. - * - * @author Roman Kennke (kennke@aicas.com) + * A view implementation that renders HTML tables. + * + * This is basically a vertical BoxView that contains the rows of the table + * and the rows are horizontal BoxViews that contain the actual columns. */ class TableView - extends javax.swing.text.TableView + extends BoxView + implements ViewFactory { + /** * Represents a single table row. */ - public class RowView extends TableRow + class RowView + extends BlockView { /** - * Creates a new instance of the <code>RowView</code>. + * Creates a new RowView. * - * @param el the element for which to create a row view + * @param el the element for the row view + */ + RowView(Element el) + { + super(el, X_AXIS); + } + + /** + * Overridden to make rows not resizable along the Y axis. + */ + public float getMaximumSpan(int axis) + { + float span; + if (axis == Y_AXIS) + span = super.getPreferredSpan(axis); + else + span = super.getMaximumSpan(axis); + return span; + } + + /** + * Calculates the overall size requirements for the row along the + * major axis. This will be the sum of the column requirements. + */ + protected SizeRequirements calculateMajorAxisRequirements(int axis, + SizeRequirements r) + { + if (r == null) + r = new SizeRequirements(); + r.minimum = totalColumnRequirements.minimum; + r.preferred = totalColumnRequirements.preferred; + r.maximum = totalColumnRequirements.maximum; + r.alignment = 0.0F; + return r; + } + + /** + * Lays out the columns in this row. */ - public RowView(Element el) + protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, + int spans[]) { - super(el); + int numCols = offsets.length; + int realColumn = 0; + for (int i = 0; i < numCols; i++) + { + View v = getView(i); + if (v instanceof CellView) + { + CellView cv = (CellView) v; + offsets[i] = columnOffsets[realColumn]; + spans[i] = 0; + for (int j = 0; j < cv.colSpan; j++, realColumn++) + { + spans[i] += columnSpans[realColumn]; + } + } + } } - + } + /** - * Get the associated style sheet from the document. - * - * @return the associated style sheet. + * A view that renders HTML table cells (TD and TH tags). */ - protected StyleSheet getStyleSheet() + class CellView + extends BlockView + { + + /** + * The number of columns that this view spans. + */ + int colSpan; + + /** + * Creates a new CellView for the specified element. + * + * @param el the element for which to create the colspan + */ + CellView(Element el) { - Document d = getElement().getDocument(); - if (d instanceof HTMLDocument) - return ((HTMLDocument) d).getStyleSheet(); - else - return null; - } + super(el, Y_AXIS); + } + + /** + * Overridden to fetch the columnSpan attibute. + */ + protected void setPropertiesFromAttributes() + { + super.setPropertiesFromAttributes(); + colSpan = 1; + AttributeSet atts = getAttributes(); + Object o = atts.getAttribute(HTML.Attribute.COLSPAN); + if (o != null) + { + try + { + colSpan = Integer.parseInt(o.toString()); + } + catch (NumberFormatException ex) + { + // Couldn't parse the colspan, assume 1. + colSpan = 1; + } + } + } } + + /** + * The attributes of this view. + */ + private AttributeSet attributes; + + /** + * The column requirements. + */ + private SizeRequirements[] columnRequirements; + + /** + * The overall requirements across all columns. + * + * Package private to avoid accessor methods. + */ + SizeRequirements totalColumnRequirements; + + /** + * The column layout, offsets. + * + * Package private to avoid accessor methods. + */ + int[] columnOffsets; + + /** + * The column layout, spans. + * + * Package private to avoid accessor methods. + */ + int[] columnSpans; + + /** + * The widths of the columns that have been explicitly specified. + */ + Length[] columnWidths; + + /** + * Indicates if the grid setup is ok. + */ + private boolean gridValid; + /** * Creates a new HTML table view for the specified element. * @@ -88,50 +224,424 @@ class TableView */ public TableView(Element el) { - super(el); + super(el, Y_AXIS); + totalColumnRequirements = new SizeRequirements(); } - + /** - * Get the associated style sheet from the document. - * - * @return the associated style sheet. + * Implementation of the ViewFactory interface for creating the + * child views correctly. */ - protected StyleSheet getStyleSheet() + public View create(Element elem) { - Document d = getElement().getDocument(); - if (d instanceof HTMLDocument) - return ((HTMLDocument) d).getStyleSheet(); - else - return null; - } - + View view = null; + AttributeSet atts = elem.getAttributes(); + Object name = atts.getAttribute(StyleConstants.NameAttribute); + if (name instanceof HTML.Tag) + { + HTML.Tag tag = (HTML.Tag) name; + if (tag == HTML.Tag.TR) + view = new RowView(elem); + else if (tag == HTML.Tag.TD || tag == HTML.Tag.TH) + view = new CellView(elem); + else if (tag == HTML.Tag.CAPTION) + view = new ParagraphView(elem); + } + + // If we haven't mapped the element, then fall back to the standard + // view factory. + if (view == null) + { + View parent = getParent(); + if (parent != null) + { + ViewFactory vf = parent.getViewFactory(); + if (vf != null) + view = vf.create(elem); + } + } + return view; + } + /** - * Creates a view for a table row. - * - * @param el the element that represents the table row - * @return a view for rendering the table row - * (and instance of {@link RowView}). + * Returns this object as view factory so that we get our TR, TD, TH + * and CAPTION subelements created correctly. */ - protected TableRow createTableRow(Element el) + public ViewFactory getViewFactory() { - return new RowView(el); - } - + return this; + } + /** - * Loads the children of the Table. This completely bypasses the ViewFactory - * and creates instances of TableRow instead. + * Returns the attributes of this view. This is overridden to provide + * the attributes merged with the CSS stuff. + */ + public AttributeSet getAttributes() + { + if (attributes == null) + attributes = getStyleSheet().getViewAttributes(this); + return attributes; + } + + /** + * Returns the stylesheet associated with this view. * - * @param vf ignored + * @return the stylesheet associated with this view */ - protected void loadChildren(ViewFactory vf) + private StyleSheet getStyleSheet() { - Element el = getElement(); - int numChildren = el.getElementCount(); - View[] rows = new View[numChildren]; - for (int i = 0; i < numChildren; ++i) + HTMLDocument doc = (HTMLDocument) getDocument(); + return doc.getStyleSheet(); + } + + /** + * Overridden to calculate the size requirements according to the + * columns distribution. + */ + protected SizeRequirements calculateMinorAxisRequirements(int axis, + SizeRequirements r) + { + updateGrid(); + calculateColumnRequirements(); + + // Calculate the horizontal requirements according to the superclass. + // This will return the maximum of the row's widths. + r = super.calculateMinorAxisRequirements(axis, r); + + // Try to set the CSS width if it fits. + AttributeSet atts = getAttributes(); + Length l = (Length) atts.getAttribute(CSS.Attribute.WIDTH); + if (l != null) + { + int width = (int) l.getValue(); + if (r.minimum < width) + r.minimum = width; + } + + // Apply the alignment. + Object o = atts.getAttribute(CSS.Attribute.TEXT_ALIGN); + r.alignment = 0.0F; + if (o != null) { - rows[i] = createTableRow(el.getElement(i)); + String al = o.toString(); + if (al.equals("left")) + r.alignment = 0.0F; + else if (al.equals("center")) + r.alignment = 0.5F; + else if (al.equals("right")) + r.alignment = 1.0F; } - replace(0, getViewCount(), rows); + + return r; + } + + /** + * Overridden to perform the table layout before calling the super + * implementation. + */ + protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, + int[] spans) + { + updateGrid(); + layoutColumns(targetSpan); + super.layoutMinorAxis(targetSpan, axis, offsets, spans); + } + + /** + * Calculates the size requirements for the columns. + */ + private void calculateColumnRequirements() + { + int numRows = getViewCount(); + totalColumnRequirements.minimum = 0; + totalColumnRequirements.preferred = 0; + totalColumnRequirements.maximum = 0; + + // In this first pass we find out a suitable total width to fit in + // all columns of all rows. + for (int r = 0; r < numRows; r++) + { + RowView rowView = (RowView) getView(r); + int numCols = rowView.getViewCount(); + + // We collect the normal (non-relative) column requirements in the + // total variable and the relative requirements in the relTotal + // variable. In the end we create the maximum of both to get the + // real requirements. + SizeRequirements total = new SizeRequirements(); + SizeRequirements relTotal = new SizeRequirements(); + float totalPercent = 0.F; + for (int c = 0; c < numCols; ) + { + View v = rowView.getView(c); + if (v instanceof CellView) + { + CellView cellView = (CellView) v; + int colSpan = cellView.colSpan; + if (colSpan > 1) + { + int cellMin = (int) cellView.getMinimumSpan(X_AXIS); + int cellPref = (int) cellView.getPreferredSpan(X_AXIS); + int cellMax = (int) cellView.getMaximumSpan(X_AXIS); + int currentMin = 0; + int currentPref = 0; + long currentMax = 0; + for (int i = 0; i < colSpan; i++) + { + SizeRequirements req = columnRequirements[c + i]; + currentMin += req.minimum; + currentPref += req.preferred; + currentMax += req.maximum; + } + int deltaMin = cellMin - currentMin; + int deltaPref = cellPref - currentPref; + int deltaMax = (int) (cellMax - currentMax); + // Distribute delta. + for (int i = 0; i < colSpan; i++) + { + SizeRequirements req = columnRequirements[c + i]; + if (deltaMin > 0) + req.minimum += deltaMin / colSpan; + if (deltaPref > 0) + req.preferred += deltaPref / colSpan; + if (deltaMax > 0) + req.maximum += deltaMax / colSpan; + if (columnWidths[c + i] == null + || ! columnWidths[c + i].isPercentage()) + { + total.minimum += req.minimum; + total.preferred += req.preferred; + total.maximum += req.maximum; + } + else + { + relTotal.minimum = + Math.max(relTotal.minimum, + (int) (req.minimum + * columnWidths[c + i].getValue())); + relTotal.preferred = + Math.max(relTotal.preferred, + (int) (req.preferred + * columnWidths[c + i].getValue())); + relTotal.maximum = + Math.max(relTotal.maximum, + (int) (req.maximum + * columnWidths[c + i].getValue())); + totalPercent += columnWidths[c + i].getValue(); + } + } + } + else + { + // Shortcut for colSpan == 1. + SizeRequirements req = columnRequirements[c]; + req.minimum = Math.max(req.minimum, + (int) cellView.getMinimumSpan(X_AXIS)); + req.preferred = Math.max(req.preferred, + (int) cellView.getPreferredSpan(X_AXIS)); + req.maximum = Math.max(req.maximum, + (int) cellView.getMaximumSpan(X_AXIS)); + if (columnWidths[c] == null + || ! columnWidths[c].isPercentage()) + { + total.minimum += columnRequirements[c].minimum; + total.preferred += columnRequirements[c].preferred; + total.maximum += columnRequirements[c].maximum; + } + else + { + relTotal.minimum = + Math.max(relTotal.minimum, + (int) (req.minimum + / columnWidths[c].getValue())); + relTotal.preferred = + Math.max(relTotal.preferred, + (int) (req.preferred + / columnWidths[c].getValue())); + relTotal.maximum = + Math.max(relTotal.maximum, + (int) (req.maximum + / columnWidths[c].getValue())); + totalPercent += columnWidths[c].getValue(); + } + } + c += colSpan; + } + else + c++; + } + + // Update the total requirements as follows: + // 1. Multiply the absolute requirements with 1 - totalPercent. This + // gives the total requirements based on the wishes of the absolute + // cells. + // 2. Take the maximum of this value and the total relative + // requirements. Now we should have enough space for whatever cell + // in this column. + // 3. Take the maximum of this value and the previous maximum value. + total.minimum *= 1.F / (1.F - totalPercent); + total.preferred *= 1.F / (1.F - totalPercent); + total.maximum *= 1.F / (1.F - totalPercent); + + int rowTotalMin = Math.max(total.minimum, relTotal.minimum); + int rowTotalPref = Math.max(total.preferred, relTotal.preferred); + int rowTotalMax = Math.max(total.maximum, relTotal.maximum); + totalColumnRequirements.minimum = + Math.max(totalColumnRequirements.minimum, rowTotalMin); + totalColumnRequirements.preferred = + Math.max(totalColumnRequirements.preferred, rowTotalPref); + totalColumnRequirements.maximum = + Math.max(totalColumnRequirements.maximum, rowTotalMax); + } + + // Now we know what we want and can fix up the actual relative + // column requirements. + int numCols = columnRequirements.length; + for (int i = 0; i < numCols; i++) + { + if (columnWidths[i] != null) + { + columnRequirements[i].minimum = (int) + columnWidths[i].getValue(totalColumnRequirements.minimum); + columnRequirements[i].preferred = (int) + columnWidths[i].getValue(totalColumnRequirements.preferred); + columnRequirements[i].maximum = (int) + columnWidths[i].getValue(totalColumnRequirements.maximum); + } + } + } + + /** + * Lays out the columns. + * + * @param targetSpan the target span into which the table is laid out + */ + private void layoutColumns(int targetSpan) + { + // Set the spans to the preferred sizes. Determine the space + // that we have to adjust the sizes afterwards. + long sumPref = 0; + int n = columnRequirements.length; + for (int i = 0; i < n; i++) + { + SizeRequirements col = columnRequirements[i]; + if (columnWidths[i] != null) + columnSpans[i] = (int) columnWidths[i].getValue(targetSpan); + else + columnSpans[i] = col.preferred; + sumPref += columnSpans[i]; + } + + // Try to adjust the spans so that we fill the targetSpan. + long diff = targetSpan - sumPref; + float factor = 0.0F; + int[] diffs = null; + if (diff != 0) + { + long total = 0; + diffs = new int[n]; + for (int i = 0; i < n; i++) + { + // Only adjust the width if we haven't set a column width here. + if (columnWidths[i] == null) + { + SizeRequirements col = columnRequirements[i]; + int span; + if (diff < 0) + { + span = col.minimum; + diffs[i] = columnSpans[i] - span; + } + else + { + span = col.maximum; + diffs[i] = span - columnSpans[i]; + } + total += span; + } + else + total += columnSpans[i]; + } + + float maxAdjust = Math.abs(total - sumPref); + factor = diff / maxAdjust; + factor = Math.min(factor, 1.0F); + factor = Math.max(factor, -1.0F); + } + + // Actually perform adjustments. + int totalOffs = 0; + for (int i = 0; i < n; i++) + { + columnOffsets[i] = totalOffs; + if (diff != 0) + { + float adjust = factor * diffs[i]; + columnSpans[i] += Math.round(adjust); + } + // Avoid overflow here. + totalOffs = (int) Math.min((long) totalOffs + (long) columnSpans[i], + Integer.MAX_VALUE); + } + } + + /** + * Updates the arrays that contain the row and column data in response + * to a change to the table structure. + */ + private void updateGrid() + { + if (! gridValid) + { + int maxColumns = 0; + int numRows = getViewCount(); + for (int r = 0; r < numRows; r++) + { + RowView rowView = (RowView) getView(r); + int numCols = rowView.getViewCount(); + maxColumns = Math.max(numCols, maxColumns); + } + columnWidths = new Length[maxColumns]; + for (int r = 0; r < numRows; r++) + { + RowView rowView = (RowView) getView(r); + int numCols = rowView.getViewCount(); + int colIndex = 0; + for (int c = 0; c < numCols; c++) + { + View v = rowView.getView(c); + if (v instanceof CellView) + { + CellView cv = (CellView) v; + Object o = + cv.getAttributes().getAttribute(CSS.Attribute.WIDTH); + if (o != null && columnWidths[colIndex] == null + && o instanceof Length) + columnWidths[colIndex]= (Length) o; + colIndex += cv.colSpan; + } + } + } + columnRequirements = new SizeRequirements[maxColumns]; + for (int i = 0; i < maxColumns; i++) + columnRequirements[i] = new SizeRequirements(); + columnOffsets = new int[maxColumns]; + columnSpans = new int[maxColumns]; + + gridValid = true; + } + } + + /** + * Overridden to restrict the table width to the preferred size. + */ + public float getMaximumSpan(int axis) + { + float span; + if (axis == X_AXIS) + span = super.getPreferredSpan(axis); + else + span = super.getMaximumSpan(axis); + return span; } } |