/******************************************************************************* * Copyright (c) 2004, 2008 John Krasnay and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * John Krasnay - initial API and implementation *******************************************************************************/ package net.sf.vex.layout; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import net.sf.vex.core.Insets; import net.sf.vex.core.IntRange; import net.sf.vex.css.CSS; import net.sf.vex.css.StyleSheet; import net.sf.vex.css.Styles; import net.sf.vex.dom.Element; /** * Box that lays out a table. */ public class TableBox extends AbstractBlockBox { /** * Class constructor. * @param element Element represented by this box. */ public TableBox(LayoutContext context, BlockBox parent, Element element) { super(context, parent, element); } public TableBox(LayoutContext context, BlockBox parent, int startOffset, int endOffset) { super(context, parent, startOffset, endOffset); } protected List createChildren(final LayoutContext context) { // Walk children: // each table-caption gets a BEB // each table-column gets a TableColumnBox // each table-column-group gets a TableColumnGroupBox // runs of others get TableBodyBox final List children = new ArrayList(); this.iterateChildrenByDisplayStyle(context.getStyleSheet(), captionOrColumnStyles, new ElementOrRangeCallback() { public void onElement(Element child, String displayStyle) { children.add(new BlockElementBox(context, TableBox.this, child)); } public void onRange(Element parent, int startOffset, int endOffset) { children.add(new TableBodyBox(context, TableBox.this, startOffset, endOffset)); } }); return children; } /** * Returns an array of widths of the table columns. These widths do not * include column spacing. */ public int[] getColumnWidths() { return this.columnWidths; } public int getHorizonalSpacing() { return this.horizonalSpacing; } public Insets getInsets(LayoutContext context, int containerWidth) { return new Insets(this.getMarginTop(), 0, this.getMarginBottom(), 0); } public int getVerticalSpacing() { return this.verticalSpacing; } public IntRange layout(LayoutContext context, int top, int bottom) { // TODO Only compute columns widths (a) if re-laying out the whole box // or (b) if the invalid child row now has more columns than us // or (c) if the invalid child row has < current column count and it // used to be the only one with a valid child row. int newColCount = this.computeColumnCount(context); if (this.columnWidths == null || newColCount != this.columnWidths.length) { this.setLayoutState(LAYOUT_REDO); } if (this.getLayoutState() == LAYOUT_REDO) { this.computeColumnWidths(context, newColCount); } return super.layout(context, top, bottom); } public void paint(LayoutContext context, int x, int y) { if (this.skipPaint(context, x, y)) { return; } this.paintChildren(context, x, y); this.paintSelectionFrame(context, x, y, true); } //============================================================ PRIVATE private static Set captionOrColumnStyles = new HashSet(); static { captionOrColumnStyles.add(CSS.TABLE_CAPTION); captionOrColumnStyles.add(CSS.TABLE_COLUMN); captionOrColumnStyles.add(CSS.TABLE_COLUMN_GROUP); } private int[] columnWidths; private int horizonalSpacing; private int verticalSpacing; private static class CountingCallback implements ElementOrRangeCallback { public int getCount() { return this.count; } public void reset() { this.count = 0; } public void onElement(Element child, String displayStyle) { this.count++; } public void onRange(Element parent, int startOffset, int endOffset) { this.count++; } private int count; } /** * Performs a quick count of this table's columns. If the count has changed, we * must re-layout the entire table. */ private int computeColumnCount(LayoutContext context) { Element tableElement = this.findContainingElement(); final int[] columnCounts = new int[1]; // work around Java's insistence on final columnCounts[0] = 0; final StyleSheet styleSheet = context.getStyleSheet(); final CountingCallback callback = new CountingCallback(); LayoutUtils.iterateTableRows(styleSheet, tableElement, this.getStartOffset(), this.getEndOffset(), new ElementOrRangeCallback() { public void onElement(Element child, String displayStyle) { LayoutUtils.iterateTableCells(styleSheet, child, callback); columnCounts[0] = Math.max(columnCounts[0], callback.getCount()); callback.reset(); } public void onRange(Element parent, int startOffset, int endOffset) { LayoutUtils.iterateTableCells(styleSheet, parent, startOffset, endOffset, callback); columnCounts[0] = Math.max(columnCounts[0], callback.getCount()); callback.reset(); } }); return columnCounts[0]; } private void computeColumnWidths(final LayoutContext context, int columnCount) { this.columnWidths = new int[columnCount]; if (columnCount == 0) { return; } this.horizonalSpacing = 0; this.verticalSpacing = 0; int myWidth = this.getWidth(); int availableWidth = myWidth; if (!this.isAnonymous()) { Styles styles = context.getStyleSheet().getStyles(this.getElement()); this.horizonalSpacing = styles.getBorderSpacing().getHorizontal(); this.verticalSpacing = styles.getBorderSpacing().getVertical(); // width available for columns // Since we apply margins/borders/padding to the TableBodyBox, they're // not reflected in the width of this box. Thus, we subtract them here availableWidth -= + styles.getMarginLeft().get(myWidth) + styles.getBorderLeftWidth() + styles.getPaddingLeft().get(myWidth) + styles.getPaddingRight().get(myWidth) + styles.getBorderRightWidth() + styles.getMarginRight().get(myWidth); } int totalColumnWidth = this.horizonalSpacing; int columnWidth = (availableWidth - this.horizonalSpacing * (columnCount + 1)) / columnCount; for (int i = 0; i < this.columnWidths.length - 1; i++) { System.err.print(" " + columnWidth); this.columnWidths[i] = columnWidth; totalColumnWidth += columnWidth + this.horizonalSpacing; } // Due to rounding errors in the expression above, we calculate the // width of the last column separately, to make it exact. this.columnWidths[this.columnWidths.length - 1] = availableWidth - totalColumnWidth - this.horizonalSpacing; } }