| /******************************************************************************* |
| * Copyright (c) 2006 Sybase, Inc. 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: |
| * Sybase, Inc. - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jst.pagedesigner.css2.layout.table; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.draw2d.ColorConstants; |
| import org.eclipse.draw2d.Graphics; |
| import org.eclipse.draw2d.IFigure; |
| import org.eclipse.draw2d.geometry.Dimension; |
| import org.eclipse.draw2d.geometry.Insets; |
| import org.eclipse.draw2d.geometry.Rectangle; |
| import org.eclipse.jst.jsf.common.ui.internal.logging.Logger; |
| import org.eclipse.jst.pagedesigner.PDPlugin; |
| import org.eclipse.jst.pagedesigner.css2.ICSSStyle; |
| import org.eclipse.jst.pagedesigner.css2.layout.CSSBlockFlowLayout; |
| import org.eclipse.jst.pagedesigner.css2.layout.CSSFigure; |
| import org.eclipse.jst.pagedesigner.css2.layout.ICSSPainter; |
| import org.eclipse.jst.pagedesigner.css2.property.ICSSPropertyID; |
| import org.eclipse.jst.pagedesigner.css2.style.ITagEditInfo; |
| import org.eclipse.swt.SWT; |
| |
| /** |
| * @see http://www.w3.org/TR/REC-CSS2/tables.html |
| * |
| * @author mengbo |
| * @version 1.5 |
| */ |
| public class CSSTableLayout2 extends CSSBlockFlowLayout implements ICSSPainter { |
| static Logger _log = PDPlugin.getLogger(CSSTableLayout2.class); |
| |
| int _hspacing; |
| |
| int _vspacing; |
| |
| int[] _columnWidths; |
| |
| int[] _rowHeights; |
| |
| Dimension _captionSize; |
| |
| // _tableInfo will be initialized in preLayout |
| TableInfo _tableInfo; |
| |
| private int _internalTableWidth; |
| |
| private int _internalTableHeight; |
| |
| private int _rowx; |
| |
| private int _rowwidth; |
| |
| /** |
| * @param flowfigure |
| */ |
| public CSSTableLayout2(CSSFigure flowfigure) { |
| super(flowfigure); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.layout.CSSBlockFlowLayout#preLayout() |
| */ |
| protected void preLayout() { |
| // super.preLayout will setup the block box. |
| super.preLayout(); |
| |
| ICSSStyle style = this.getCSSStyle(); |
| |
| _hspacing = _vspacing = 3; // default value |
| |
| if (style != null) { |
| Object borderspacing = style |
| .getStyleProperty(ICSSPropertyID.ATTR_BORDER_SPACING); |
| if (borderspacing instanceof int[]) { |
| int[] intvalues = (int[]) borderspacing; |
| _hspacing = intvalues[0]; |
| _vspacing = intvalues[1]; |
| } else { |
| ITagEditInfo info = (ITagEditInfo) style |
| .getAdapter(ITagEditInfo.class); |
| if (info != null && info.needTableDecorator()) { |
| // default decorating value. to make things look more |
| // separated. |
| if (_hspacing < 5) { |
| _hspacing = 5; |
| } |
| if (_vspacing < 5) { |
| _vspacing = 5; |
| } |
| } |
| } |
| } |
| |
| // TODO: support caption |
| _tableInfo = new TableInfo(getCSSFigure()); |
| |
| // construct the table structure. |
| _tableInfo.constructTable(); |
| |
| // calculate the user specified width/height for table and cells. |
| // contentWidth is the user specified content width. If <= 0 means no |
| // user |
| // specification. |
| int contentWidth = this._blockBox.getContentWidth(); |
| int availableWidth = this._blockBox.getRecommendedContentWidth(); |
| int contentHeight = this._blockBox.getContentHeight(); |
| |
| _tableInfo.calculateWidth(contentWidth, availableWidth); |
| _tableInfo.calculateHeight(contentHeight); |
| |
| int columnCount = _tableInfo.getColumnCount(); |
| |
| int columnMinWidths[] = new int[columnCount]; |
| int columnMaxWidths[] = new int[columnCount]; |
| |
| // For each column, determine a maximum and minimum column width from |
| // the cells that span only that column. The minimum is that required by |
| // the cell with the largest minimum cell width (or the column 'width', |
| // whichever is larger). The maximum is that required by the cell with |
| // the |
| // largest maximum cell width (or the column 'width', whichever is |
| // larger). |
| List cells = _tableInfo.getCells(); |
| for (int i = 0, size = cells.size(); i < size; i++) { |
| TableCellInfo cellinfo = (TableCellInfo) cells.get(i); |
| if (cellinfo.getColSpan() == 1) { |
| int column = cellinfo.getColumnIndex(); |
| Dimension mincw = cellinfo.getMinCWDimension(); |
| Dimension maxcw = cellinfo.getMaxCWDimension(); |
| if (maxcw.width < mincw.width) { |
| maxcw.width = mincw.width; |
| } |
| if (mincw.width > columnMinWidths[column]) { |
| columnMinWidths[column] = mincw.width; |
| } |
| if (maxcw.width > columnMaxWidths[column]) { |
| columnMaxWidths[column] = maxcw.width; |
| } |
| } |
| } |
| // For caption, determine a maximum and minimum width from it. |
| int captionWidth = 0; |
| if (_tableInfo._caption != null) { |
| captionWidth = _tableInfo._caption.getDimension().width; |
| } |
| |
| // For each cell that spans more than one column, increase the |
| // minimum widths of the columns it spans so that together, they |
| // are at least as wide as the cell. Do the same for the maximum |
| // widths. If possible, widen all spanned columns by approximately |
| // the same amount. |
| for (int i = 0, size = cells.size(); i < size; i++) { |
| TableCellInfo cellinfo = (TableCellInfo) cells.get(i); |
| int colspan = cellinfo.getColSpan(); |
| if (colspan > 1) { |
| int column = cellinfo.getColumnIndex(); |
| Dimension mincw = cellinfo.getMinCWDimension(); |
| Dimension maxcw = cellinfo.getMaxCWDimension(); |
| |
| adjustWidth(column, colspan, mincw.width, columnMinWidths); |
| adjustWidth(column, colspan, maxcw.width, columnMaxWidths); |
| } |
| } |
| |
| int sigmaMinWidth = 0; |
| int sigmaMaxWidth = 0; |
| for (int i = 0; i < columnMinWidths.length; i++) { |
| sigmaMinWidth += columnMinWidths[i]; |
| if (columnMaxWidths[i] == Integer.MAX_VALUE) { |
| sigmaMaxWidth = Integer.MAX_VALUE; |
| } else if (sigmaMaxWidth != Integer.MAX_VALUE) { |
| sigmaMaxWidth += columnMaxWidths[i]; |
| if (sigmaMaxWidth < 0) { |
| sigmaMaxWidth = Integer.MAX_VALUE; |
| } |
| } |
| } |
| int spacingall = (columnMinWidths.length + 1) * _hspacing; |
| sigmaMinWidth += spacingall; |
| if (sigmaMaxWidth != Integer.MAX_VALUE) { |
| sigmaMaxWidth += spacingall; |
| if (sigmaMaxWidth < 0) { |
| sigmaMaxWidth = Integer.MAX_VALUE; |
| } |
| } |
| |
| int tableWidth = _tableInfo.getTableWidth(); |
| if (tableWidth > 0) { |
| // If the 'table' or 'inline-table' element's 'width' property has a |
| // specified value (W) other than 'auto', the property's computed |
| // value |
| // is the greater of W and the minimum width required by all the |
| // columns |
| // plus cell spacing or borders (MIN). If W is greater than MIN, the |
| // extra |
| // width should be distributed over the columns. |
| int maxMin = Math.max(captionWidth, sigmaMinWidth); |
| if (maxMin >= tableWidth) { |
| tableWidth = maxMin; |
| } |
| distribute(tableWidth - sigmaMinWidth, columnMinWidths, |
| columnMaxWidths); |
| } else { |
| // If the 'table' or 'inline-table' element has 'width: auto', the |
| // computed |
| // table width is the greater of the table's containing block width |
| // and MIN. |
| // However, if the maximum width required by the columns plus cell |
| // spacing or |
| // borders (MAX) is less than that of the containing block, use MAX. |
| // int availableWidth = this.getCurrentLine().getAvailableWidth(); |
| int maxMin = Math.max(captionWidth, sigmaMaxWidth); |
| if (maxMin <= availableWidth) { |
| // TODO: if _tableInfo.hasWidthPercentage, then we need take |
| // that into consideration |
| // to distribute the column width. Left to next version. |
| tableWidth = maxMin; |
| // columnMinWidths = columnMaxWidths; |
| } else { |
| tableWidth = availableWidth; |
| } |
| distribute(tableWidth - sigmaMinWidth, columnMinWidths, |
| columnMaxWidths); |
| } |
| |
| // now columnMinWidths contains width for each column |
| _columnWidths = columnMinWidths; |
| |
| // ok, we have finished calculating column width. |
| // next we need to find out row heights. |
| _rowHeights = new int[_tableInfo.getRowCount()]; |
| |
| // first find out those TR that has height settings and use them. |
| List rows = _tableInfo.getRows(); |
| for (int i = 0, size = rows.size(); i < size && i < _rowHeights.length; i++) { |
| TableRowInfo rowInfo = (TableRowInfo) rows.get(i); |
| if (rowInfo.getSpecifiedRowHeight() > 0) { |
| _rowHeights[i] = rowInfo.getSpecifiedRowHeight(); |
| } |
| } |
| |
| // First the cells don't span multiple rows. |
| cells = _tableInfo.getCells(); |
| for (int i = 0, size = cells.size(); i < size; i++) { |
| TableCellInfo cellinfo = (TableCellInfo) cells.get(i); |
| IFigure figure = cellinfo.getFigure(); |
| int rowspan = cellinfo.getRowSpan(); |
| if (rowspan == 1) { |
| int cellWidth = getCellWidth(cellinfo, _columnWidths); |
| Dimension d = figure.getPreferredSize(cellWidth, cellinfo |
| .getHeight()); |
| if (d.height > _rowHeights[cellinfo.getRowIndex()]) { |
| _rowHeights[cellinfo.getRowIndex()] = d.height; |
| } |
| } |
| } |
| |
| // Next those cells span multiple rows. |
| cells = _tableInfo.getCells(); |
| for (int i = 0, size = cells.size(); i < size; i++) { |
| TableCellInfo cellinfo = (TableCellInfo) cells.get(i); |
| IFigure figure = cellinfo.getFigure(); |
| int rowspan = cellinfo.getRowSpan(); |
| if (rowspan > 1) { |
| int cellWidth = getCellWidth(cellinfo, _columnWidths); |
| Dimension d = figure.getPreferredSize(cellWidth, cellinfo |
| .getHeight()); |
| if (d.height > getCellHeight(cellinfo, _rowHeights)) { |
| adjustHeight(cellinfo.getRowIndex(), rowspan, d.height, |
| _rowHeights); |
| } |
| } |
| } |
| |
| // Next we may need distribute height. |
| int sigmaHeight = (_tableInfo.getRowCount() + 1) * _vspacing; |
| for (int i = 0; i < _rowHeights.length; i++) { |
| sigmaHeight += _rowHeights[i]; |
| } |
| if (sigmaHeight < contentHeight) { |
| distributeHeights(contentHeight - sigmaHeight, _rowHeights); |
| } |
| |
| // now we have calculated the width and height of all cells. |
| // FIXME: border? |
| Insets insets = (style == null ? new Insets() : style.getBorderInsets() |
| .getAdded(style.getPaddingInsets())); |
| _internalTableWidth = (_tableInfo.getColumnCount() + 1) * _hspacing; |
| for (int i = 0; i < _columnWidths.length; i++) { |
| _internalTableWidth += _columnWidths[i]; |
| } |
| int minWidth = getLengthValue(style, ICSSPropertyID.ATTR_MIN_WIDTH); |
| _internalTableWidth = _internalTableWidth > minWidth ? _internalTableWidth |
| : minWidth; |
| |
| _blockBox.setWidth(_internalTableWidth + insets.getWidth()); |
| _internalTableHeight = (_tableInfo.getRowCount() + 1) * _vspacing; |
| for (int i = 0; i < _rowHeights.length; i++) { |
| _internalTableHeight += _rowHeights[i]; |
| } |
| int minHeight = getLengthValue(style, ICSSPropertyID.ATTR_MIN_HEIGHT); |
| _internalTableHeight = _internalTableHeight > minHeight ? _internalTableHeight |
| : minHeight; |
| |
| int captionHeight = 0; |
| if (_tableInfo._caption != null) { |
| _captionSize = _tableInfo._caption.getFigure().getPreferredSize( |
| _internalTableWidth, SWT.DEFAULT); |
| captionHeight = _captionSize.height; |
| } else { |
| _captionSize = null; |
| } |
| _internalTableHeight += captionHeight; |
| |
| _blockBox.setHeight(_internalTableHeight + insets.getHeight()); |
| |
| _rowwidth = _internalTableWidth - 2 * _hspacing; |
| _rowx = _hspacing; // XXX: table border width left? |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.layout.CSSBlockFlowLayout#endBlock() |
| */ |
| protected void endBlock() { |
| _blockBox.setWidth(_internalTableWidth |
| + _blockBox.getBorderPaddingWidth()); |
| _blockBox.setHeight(_internalTableHeight |
| + _blockBox.getBorderPaddingHeight()); |
| super.endBlock(); |
| } |
| |
| // |
| // /** |
| // * when some of the column has percentage width, and sigmaMax smaller than |
| // container, |
| // * @param containerWidth |
| // * @param columnMinWidths |
| // * @param columnMaxWidths |
| // * @return |
| // */ |
| // private int distribute2(int containerWidth, int[] columnMinWidths, int[] |
| // columnMaxWidths) |
| // { |
| // |
| // } |
| // |
| /** |
| * Distribute the additional width to columnMinWidths, using max width as a |
| * possible reference on how to distribute. |
| * |
| * @param toDistribute |
| * @param columnMinWidths |
| * @param columnMaxWidths |
| */ |
| private void distribute(int toDistribute, int[] columnMinWidths, |
| int[] columnMaxWidths) { |
| if (toDistribute <= 0) |
| return; |
| if (columnMinWidths.length == 0) |
| return; |
| |
| int[] delta = new int[columnMinWidths.length]; |
| int sigmaDelta = 0; |
| for (int i = 0; i < columnMinWidths.length && toDistribute > 0; i++) { |
| if (_tableInfo._widthSpecified[i]) { |
| delta[i] = 0; |
| } else { |
| delta[i] = columnMaxWidths[i] - columnMinWidths[i]; |
| if (delta[i] <= 0) { |
| delta[i] = 0; |
| } |
| sigmaDelta += delta[i]; |
| } |
| } |
| if (sigmaDelta == 0) { |
| // should not happen, but anyway, distribute all to the last column |
| // columnMinWidths[columnMinWidths.length-1] += toDistribute; |
| averageDeltaToCell(columnMinWidths, toDistribute); |
| } else { |
| int left = toDistribute; |
| for (int i = 0; i < columnMinWidths.length - 1; i++) { |
| if (delta[i] > 0) { |
| int add = delta[i] * toDistribute / sigmaDelta; |
| left -= add; |
| columnMinWidths[i] += add; |
| } |
| } |
| columnMinWidths[columnMinWidths.length - 1] += left; |
| } |
| } |
| |
| private void averageDeltaToCell(int[] columnMinWidths, int toDistribute) { |
| |
| if (toDistribute <= 0) { |
| return; |
| } |
| ArrayList list = new ArrayList(); |
| for (int i = 0; i < columnMinWidths.length; i++) { |
| if (!_tableInfo._widthSpecified[i]) { |
| list.add(new Integer(i)); |
| } |
| } |
| if (list.size() == 0) { |
| for (int i = 0; i < columnMinWidths.length; i++) { |
| list.add(new Integer(i)); |
| } |
| } |
| int padding = toDistribute / list.size(); |
| int left = toDistribute % list.size(); |
| for (int i = 0, n = list.size(); i < n; i++) { |
| columnMinWidths[((Integer) list.get(i)).intValue()] += padding; |
| } |
| if (left > 0) { |
| for (int i = 0; i < left; i++) { |
| columnMinWidths[((Integer) list.get(i)).intValue()] += 1; |
| } |
| } |
| } |
| |
| /** |
| * @param i |
| * @param heights |
| */ |
| private void distributeHeights(int toDistribute, int[] heights) { |
| if (heights.length == 0) |
| return; |
| int eachDelta = toDistribute / heights.length; |
| for (int i = 0; i < heights.length - 1; i++) { |
| heights[i] += eachDelta; |
| } |
| heights[heights.length - 1] += toDistribute - (heights.length - 1) |
| * eachDelta; |
| } |
| |
| /** |
| * @param cellinfo |
| * @param heights |
| * @return |
| */ |
| public int getCellHeight(TableCellInfo cellinfo, int[] heights) { |
| int rowIndex = cellinfo.getRowIndex(); |
| int rowspan = cellinfo.getRowSpan(); |
| int h = 0; |
| for (int i = 0; i < rowspan; i++) { |
| h += heights[rowIndex + i]; |
| } |
| h += (rowspan - 1) * _vspacing; |
| return h; |
| } |
| |
| /** |
| * @param cellinfo |
| * @param widths |
| * @return |
| */ |
| public int getCellWidth(TableCellInfo cellinfo, int[] widths) { |
| int columnIndex = cellinfo.getColumnIndex(); |
| int colspan = cellinfo.getColSpan(); |
| int w = 0; |
| for (int i = 0; i < colspan; i++) { |
| w += widths[columnIndex + i]; |
| } |
| w += (colspan - 1) * _hspacing; |
| return w; |
| } |
| |
| /** |
| * @param column |
| * the start column |
| * @param colspan |
| * number of columns |
| * @param width |
| * desired width |
| * @param columnWidths |
| * current columns widths. After the adjust, need make sure the |
| * columnWidths to be bigger than desired width |
| */ |
| private void adjustWidth(int column, int colspan, int width, |
| int[] columnWidths) { |
| adjustSpan(column, colspan, width, columnWidths, _hspacing); |
| } |
| |
| /** |
| * @see #adjustWidth(int, int, int, int[]) |
| */ |
| private void adjustHeight(int rowIndex, int rowspan, int height, |
| int[] heights) { |
| adjustSpan(rowIndex, rowspan, height, heights, _vspacing); |
| } |
| |
| static private void adjustSpan(int column, int colspan, int width, |
| int[] columnWidths, int spacing) { |
| int spanwidth = 0; |
| for (int i = 0; i < colspan; i++) { |
| spanwidth += columnWidths[column + i]; |
| } |
| // XXX: vspacing here? |
| spanwidth += (colspan - 1) * spacing; |
| |
| if (spanwidth >= width) { |
| return; |
| } |
| int delta = width - spanwidth; |
| int deltaeach = delta / colspan; |
| for (int i = 0; i < colspan - 1; i++) { |
| columnWidths[column + i] += deltaeach; |
| } |
| columnWidths[column + colspan - 1] += (delta - (colspan - 1) |
| * deltaeach); |
| } |
| |
| /** |
| * @return |
| */ |
| public int[] getRowHeights() { |
| return _rowHeights; |
| } |
| |
| /** |
| * @return |
| */ |
| public int[] getColumnWidths() { |
| return _columnWidths; |
| } |
| |
| /** |
| * @return |
| */ |
| public int getVSpacing() { |
| return _vspacing; |
| } |
| |
| /** |
| * @return |
| */ |
| public int getHSpacing() { |
| return _hspacing; |
| } |
| |
| /** |
| * @param figure |
| * @return |
| */ |
| public TableRowInfo getRowInfo(CSSFigure figure) { |
| return _tableInfo.findRowInfo(figure); |
| } |
| |
| public TableCaptionInfo getCaptionInfo() { |
| return _tableInfo._caption; |
| } |
| |
| /** |
| * @param figure |
| * @return |
| */ |
| public TableRowGroupInfo getGroupInfo(CSSFigure figure) { |
| return _tableInfo.findGroupInfo(figure); |
| } |
| |
| /** |
| * @return |
| */ |
| public int getRowX() { |
| return _rowx; |
| } |
| |
| /** |
| * @return |
| */ |
| public int getRowWidth() { |
| return _rowwidth; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.layout.CSSBlockFlowLayout#shouldExpand() |
| */ |
| public boolean shouldExpand() { |
| return false; |
| } |
| |
| public Dimension getCaptionSize() { |
| return _captionSize; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jst.pagedesigner.css2.layout.ICSSPainter#paintFigure(org.eclipse.draw2d.Graphics) |
| */ |
| public void paintFigure(Graphics g) { |
| ICSSStyle style = this.getCSSStyle(); |
| if (style != null) { |
| ITagEditInfo info = (ITagEditInfo) style |
| .getAdapter(ITagEditInfo.class); |
| if (info != null && info.needTableDecorator()) { |
| List cells = _tableInfo.getCells(); |
| for (int i = 0, size = cells.size(); i < size; i++) { |
| TableCellInfo cellInfo = (TableCellInfo) cells.get(i); |
| IFigure cellfigure = cellInfo.getFigure(); |
| Rectangle rect = cellfigure.getBounds().getCopy(); |
| rect = rect.expand(1, 1); |
| g.setLineStyle(Graphics.LINE_SOLID); |
| g.setLineWidth(1); |
| g.setForegroundColor(ColorConstants.lightGray); |
| g.drawRectangle(rect); |
| } |
| } |
| } |
| } |
| |
| } |