1 /*******************************************************************************
\r
2 * Copyright (c) 2006 Sybase, Inc. and others.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * Sybase, Inc. - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.eclipse.jst.pagedesigner.css2.layout.table;
\r
14 import java.util.ArrayList;
\r
15 import java.util.List;
\r
17 import org.eclipse.draw2d.ColorConstants;
\r
18 import org.eclipse.draw2d.Graphics;
\r
19 import org.eclipse.draw2d.IFigure;
\r
20 import org.eclipse.draw2d.geometry.Dimension;
\r
21 import org.eclipse.draw2d.geometry.Insets;
\r
22 import org.eclipse.draw2d.geometry.Rectangle;
\r
23 import org.eclipse.jst.jsf.common.ui.internal.logging.Logger;
\r
24 import org.eclipse.jst.pagedesigner.PDPlugin;
\r
25 import org.eclipse.jst.pagedesigner.css2.ICSSStyle;
\r
26 import org.eclipse.jst.pagedesigner.css2.layout.CSSBlockFlowLayout;
\r
27 import org.eclipse.jst.pagedesigner.css2.layout.CSSFigure;
\r
28 import org.eclipse.jst.pagedesigner.css2.layout.ICSSPainter;
\r
29 import org.eclipse.jst.pagedesigner.css2.property.ICSSPropertyID;
\r
30 import org.eclipse.jst.pagedesigner.css2.style.ITagEditInfo;
\r
31 import org.eclipse.swt.SWT;
\r
34 * see also http://www.w3.org/TR/REC-CSS2/tables.html
\r
39 public class CSSTableLayout2 extends CSSBlockFlowLayout implements ICSSPainter {
\r
40 static Logger _log = PDPlugin.getLogger(CSSTableLayout2.class);
\r
46 int[] _columnWidths;
\r
50 Dimension _captionSize;
\r
52 // _tableInfo will be initialized in preLayout
\r
53 TableInfo _tableInfo;
\r
55 private int _internalTableWidth;
\r
57 private int _internalTableHeight;
\r
61 private int _rowwidth;
\r
66 public CSSTableLayout2(CSSFigure flowfigure) {
\r
73 * @see org.eclipse.jst.pagedesigner.css2.layout.CSSBlockFlowLayout#preLayout()
\r
75 protected void preLayout() {
\r
76 // super.preLayout will setup the block box.
\r
79 ICSSStyle style = this.getCSSStyle();
\r
81 _hspacing = _vspacing = 3; // default value
\r
83 if (style != null) {
\r
84 Object borderspacing = style
\r
85 .getStyleProperty(ICSSPropertyID.ATTR_BORDER_SPACING);
\r
86 if (borderspacing instanceof int[]) {
\r
87 int[] intvalues = (int[]) borderspacing;
\r
88 _hspacing = intvalues[0];
\r
89 _vspacing = intvalues[1];
\r
91 ITagEditInfo info = (ITagEditInfo) style
\r
92 .getAdapter(ITagEditInfo.class);
\r
93 if (info != null && info.needTableDecorator()) {
\r
94 // default decorating value. to make things look more
\r
96 if (_hspacing < 5) {
\r
99 if (_vspacing < 5) {
\r
106 // TODO: support caption
\r
107 _tableInfo = new TableInfo(getCSSFigure());
\r
109 // construct the table structure.
\r
110 _tableInfo.constructTable();
\r
112 // calculate the user specified width/height for table and cells.
\r
113 // contentWidth is the user specified content width. If <= 0 means no
\r
116 int contentWidth = this._blockBox.getContentWidth();
\r
117 int availableWidth = this._blockBox.getRecommendedContentWidth();
\r
118 int contentHeight = this._blockBox.getContentHeight();
\r
120 _tableInfo.calculateWidth(contentWidth, availableWidth);
\r
121 _tableInfo.calculateHeight(contentHeight);
\r
123 int columnCount = _tableInfo.getColumnCount();
\r
125 int columnMinWidths[] = new int[columnCount];
\r
126 int columnMaxWidths[] = new int[columnCount];
\r
128 // For each column, determine a maximum and minimum column width from
\r
129 // the cells that span only that column. The minimum is that required by
\r
130 // the cell with the largest minimum cell width (or the column 'width',
\r
131 // whichever is larger). The maximum is that required by the cell with
\r
133 // largest maximum cell width (or the column 'width', whichever is
\r
135 List cells = _tableInfo.getCells();
\r
136 for (int i = 0, size = cells.size(); i < size; i++) {
\r
137 TableCellInfo cellinfo = (TableCellInfo) cells.get(i);
\r
138 if (cellinfo.getColSpan() == 1) {
\r
139 int column = cellinfo.getColumnIndex();
\r
140 Dimension mincw = cellinfo.getMinCWDimension();
\r
141 Dimension maxcw = cellinfo.getMaxCWDimension();
\r
142 if (maxcw.width < mincw.width) {
\r
143 maxcw.width = mincw.width;
\r
145 if (mincw.width > columnMinWidths[column]) {
\r
146 columnMinWidths[column] = mincw.width;
\r
148 if (maxcw.width > columnMaxWidths[column]) {
\r
149 columnMaxWidths[column] = maxcw.width;
\r
153 // For caption, determine a maximum and minimum width from it.
\r
154 int captionWidth = 0;
\r
155 if (_tableInfo.getCaption() != null) {
\r
156 captionWidth = _tableInfo.getCaption().getDimension().width;
\r
159 // For each cell that spans more than one column, increase the
\r
160 // minimum widths of the columns it spans so that together, they
\r
161 // are at least as wide as the cell. Do the same for the maximum
\r
162 // widths. If possible, widen all spanned columns by approximately
\r
163 // the same amount.
\r
164 for (int i = 0, size = cells.size(); i < size; i++) {
\r
165 TableCellInfo cellinfo = (TableCellInfo) cells.get(i);
\r
166 int colspan = cellinfo.getColSpan();
\r
168 int column = cellinfo.getColumnIndex();
\r
169 Dimension mincw = cellinfo.getMinCWDimension();
\r
170 Dimension maxcw = cellinfo.getMaxCWDimension();
\r
172 adjustWidth(column, colspan, mincw.width, columnMinWidths);
\r
173 adjustWidth(column, colspan, maxcw.width, columnMaxWidths);
\r
177 int sigmaMinWidth = 0;
\r
178 int sigmaMaxWidth = 0;
\r
179 for (int i = 0; i < columnMinWidths.length; i++) {
\r
180 sigmaMinWidth += columnMinWidths[i];
\r
181 if (columnMaxWidths[i] == Integer.MAX_VALUE) {
\r
182 sigmaMaxWidth = Integer.MAX_VALUE;
\r
183 } else if (sigmaMaxWidth != Integer.MAX_VALUE) {
\r
184 sigmaMaxWidth += columnMaxWidths[i];
\r
185 if (sigmaMaxWidth < 0) {
\r
186 sigmaMaxWidth = Integer.MAX_VALUE;
\r
190 int spacingall = (columnMinWidths.length + 1) * _hspacing;
\r
191 sigmaMinWidth += spacingall;
\r
192 if (sigmaMaxWidth != Integer.MAX_VALUE) {
\r
193 sigmaMaxWidth += spacingall;
\r
194 if (sigmaMaxWidth < 0) {
\r
195 sigmaMaxWidth = Integer.MAX_VALUE;
\r
199 int tableWidth = _tableInfo.getTableWidth();
\r
200 if (tableWidth > 0) {
\r
201 // If the 'table' or 'inline-table' element's 'width' property has a
\r
202 // specified value (W) other than 'auto', the property's computed
\r
204 // is the greater of W and the minimum width required by all the
\r
206 // plus cell spacing or borders (MIN). If W is greater than MIN, the
\r
208 // width should be distributed over the columns.
\r
209 int maxMin = Math.max(captionWidth, sigmaMinWidth);
\r
210 if (maxMin >= tableWidth) {
\r
211 tableWidth = maxMin;
\r
213 distribute(tableWidth - sigmaMinWidth, columnMinWidths,
\r
216 // If the 'table' or 'inline-table' element has 'width: auto', the
\r
218 // table width is the greater of the table's containing block width
\r
220 // However, if the maximum width required by the columns plus cell
\r
222 // borders (MAX) is less than that of the containing block, use MAX.
\r
223 // int availableWidth = this.getCurrentLine().getAvailableWidth();
\r
224 int maxMin = Math.max(captionWidth, sigmaMaxWidth);
\r
225 if (maxMin <= availableWidth) {
\r
226 // TODO: if _tableInfo.hasWidthPercentage, then we need take
\r
227 // that into consideration
\r
228 // to distribute the column width. Left to next version.
\r
229 tableWidth = maxMin;
\r
230 // columnMinWidths = columnMaxWidths;
\r
232 tableWidth = availableWidth;
\r
234 distribute(tableWidth - sigmaMinWidth, columnMinWidths,
\r
238 // now columnMinWidths contains width for each column
\r
239 _columnWidths = columnMinWidths;
\r
241 // ok, we have finished calculating column width.
\r
242 // next we need to find out row heights.
\r
243 _rowHeights = new int[_tableInfo.getRowCount()];
\r
245 // first find out those TR that has height settings and use them.
\r
246 List rows = _tableInfo.getRows();
\r
247 for (int i = 0, size = rows.size(); i < size && i < _rowHeights.length; i++) {
\r
248 TableRowInfo rowInfo = (TableRowInfo) rows.get(i);
\r
249 if (rowInfo.getSpecifiedRowHeight() > 0) {
\r
250 _rowHeights[i] = rowInfo.getSpecifiedRowHeight();
\r
254 // First the cells don't span multiple rows.
\r
255 cells = _tableInfo.getCells();
\r
256 for (int i = 0, size = cells.size(); i < size; i++) {
\r
257 TableCellInfo cellinfo = (TableCellInfo) cells.get(i);
\r
258 IFigure figure = cellinfo.getFigure();
\r
259 int rowspan = cellinfo.getRowSpan();
\r
260 if (rowspan == 1) {
\r
261 int cellWidth = getCellWidth(cellinfo, _columnWidths);
\r
262 Dimension d = figure.getPreferredSize(cellWidth, cellinfo
\r
264 if (d.height > _rowHeights[cellinfo.getRowIndex()]) {
\r
265 _rowHeights[cellinfo.getRowIndex()] = d.height;
\r
270 // Next those cells span multiple rows.
\r
271 cells = _tableInfo.getCells();
\r
272 for (int i = 0, size = cells.size(); i < size; i++) {
\r
273 TableCellInfo cellinfo = (TableCellInfo) cells.get(i);
\r
274 IFigure figure = cellinfo.getFigure();
\r
275 int rowspan = cellinfo.getRowSpan();
\r
277 int cellWidth = getCellWidth(cellinfo, _columnWidths);
\r
278 Dimension d = figure.getPreferredSize(cellWidth, cellinfo
\r
280 if (d.height > getCellHeight(cellinfo, _rowHeights)) {
\r
281 adjustHeight(cellinfo.getRowIndex(), rowspan, d.height,
\r
287 // Next we may need distribute height.
\r
288 int sigmaHeight = (_tableInfo.getRowCount() + 1) * _vspacing;
\r
289 for (int i = 0; i < _rowHeights.length; i++) {
\r
290 sigmaHeight += _rowHeights[i];
\r
292 if (sigmaHeight < contentHeight) {
\r
293 distributeHeights(contentHeight - sigmaHeight, _rowHeights);
\r
296 // now we have calculated the width and height of all cells.
\r
298 Insets insets = (style == null ? new Insets() : style.getBorderInsets()
\r
299 .getAdded(style.getPaddingInsets()));
\r
300 _internalTableWidth = (_tableInfo.getColumnCount() + 1) * _hspacing;
\r
301 for (int i = 0; i < _columnWidths.length; i++) {
\r
302 _internalTableWidth += _columnWidths[i];
\r
304 int minWidth = getLengthValue(style, ICSSPropertyID.ATTR_MIN_WIDTH);
\r
305 _internalTableWidth = _internalTableWidth > minWidth ? _internalTableWidth
\r
308 _blockBox.setWidth(_internalTableWidth + insets.getWidth());
\r
309 _internalTableHeight = (_tableInfo.getRowCount() + 1) * _vspacing;
\r
310 for (int i = 0; i < _rowHeights.length; i++) {
\r
311 _internalTableHeight += _rowHeights[i];
\r
313 int minHeight = getLengthValue(style, ICSSPropertyID.ATTR_MIN_HEIGHT);
\r
314 _internalTableHeight = _internalTableHeight > minHeight ? _internalTableHeight
\r
317 int captionHeight = 0;
\r
318 if (_tableInfo.getCaption() != null) {
\r
319 _captionSize = _tableInfo.getCaption().getFigure().getPreferredSize(
\r
320 _internalTableWidth, SWT.DEFAULT);
\r
321 captionHeight = _captionSize.height;
\r
323 _captionSize = null;
\r
325 _internalTableHeight += captionHeight;
\r
327 _blockBox.setHeight(_internalTableHeight + insets.getHeight());
\r
329 _rowwidth = _internalTableWidth - 2 * _hspacing;
\r
330 _rowx = _hspacing; // XXX: table border width left?
\r
336 * @see org.eclipse.jst.pagedesigner.css2.layout.CSSBlockFlowLayout#endBlock()
\r
338 protected void endBlock() {
\r
339 _blockBox.setWidth(_internalTableWidth
\r
340 + _blockBox.getBorderPaddingWidth());
\r
341 _blockBox.setHeight(_internalTableHeight
\r
342 + _blockBox.getBorderPaddingHeight());
\r
348 // * when some of the column has percentage width, and sigmaMax smaller than
\r
350 // * @param containerWidth
\r
351 // * @param columnMinWidths
\r
352 // * @param columnMaxWidths
\r
355 // private int distribute2(int containerWidth, int[] columnMinWidths, int[]
\r
356 // columnMaxWidths)
\r
362 * Distribute the additional width to columnMinWidths, using max width as a
\r
363 * possible reference on how to distribute.
\r
365 * @param toDistribute
\r
366 * @param columnMinWidths
\r
367 * @param columnMaxWidths
\r
369 private void distribute(int toDistribute, int[] columnMinWidths,
\r
370 int[] columnMaxWidths) {
\r
371 if (toDistribute <= 0)
\r
373 if (columnMinWidths.length == 0)
\r
376 int[] delta = new int[columnMinWidths.length];
\r
377 int sigmaDelta = 0;
\r
378 for (int i = 0; i < columnMinWidths.length; i++) {
\r
379 if (_tableInfo.getWidthSpecified()[i]) {
\r
382 delta[i] = columnMaxWidths[i] - columnMinWidths[i];
\r
383 if (delta[i] <= 0) {
\r
386 sigmaDelta += delta[i];
\r
390 // re-calculate the width of columns that use a percentage
\r
391 int[] widthPercentages = _tableInfo.getWidthPercentages();
\r
392 int[] calculatedWidths = new int[columnMaxWidths.length];
\r
393 int percentageWidthsTotal = 0;
\r
394 for (int i=0; i < widthPercentages.length; i++) {
\r
395 if (widthPercentages[i] > 0) {
\r
396 // add the widths of the percent width columns
\r
397 // back into the available pool
\r
398 toDistribute += columnMinWidths[i];
\r
402 for (int i=0; i < widthPercentages.length; i++) {
\r
403 if (widthPercentages[i] > 0) {
\r
404 double val = toDistribute * (widthPercentages[i] / 100.0);
\r
405 calculatedWidths[i] = (int) val;
\r
406 if (calculatedWidths[i] < columnMinWidths[i]) {
\r
407 // percent width is too small, so use
\r
408 // the columnMinWidth instead
\r
409 calculatedWidths[i] = columnMinWidths[i];
\r
411 percentageWidthsTotal += calculatedWidths[i];
\r
413 calculatedWidths[i] = 0;
\r
417 if (percentageWidthsTotal > toDistribute) {
\r
418 // calculated width is too large, so shrink the columns
\r
419 // to fit the available space
\r
420 int widthColumnCount = 0;
\r
421 for (int i=0; i < widthPercentages.length; i++) {
\r
422 if (widthPercentages[i] > 0) {
\r
423 widthColumnCount++;
\r
427 int extraSpace = percentageWidthsTotal - toDistribute;
\r
428 int shrinkBy = (int)
\r
429 Math.ceil((double) extraSpace / (double) widthColumnCount);
\r
431 for (int i=0; i < calculatedWidths.length; i++) {
\r
432 if (calculatedWidths[i] > 0) {
\r
433 calculatedWidths[i] -= shrinkBy;
\r
438 // adjust the columnMinWidth values to compensate for the
\r
439 // calculated percentages
\r
440 for (int i=0; i < calculatedWidths.length; i++) {
\r
441 // if column size was calculated, then re-calculate the delta
\r
442 if (calculatedWidths[i] > 0) {
\r
443 // remove the previous calculation from the sigmaDelta
\r
444 int len = columnMaxWidths[i] - columnMinWidths[i];
\r
451 // change the minSize to the calculated size
\r
452 columnMinWidths[i] = calculatedWidths[i];
\r
453 toDistribute -= columnMinWidths[i];
\r
457 if (sigmaDelta == 0) {
\r
458 // may happen with percent width column calculations.
\r
459 // find out how much space is left and distribute it
\r
460 // equally to all columns that are not fixed-width.
\r
461 int extraSpace = toDistribute;
\r
462 for (int i=0; i < columnMinWidths.length; i++) {
\r
463 extraSpace -= columnMinWidths[i];
\r
466 averageDeltaToCell(columnMinWidths, extraSpace);
\r
468 int left = toDistribute;
\r
469 for (int i = 0; i < columnMinWidths.length - 1; i++) {
\r
470 if (delta[i] > 0) {
\r
471 int add = delta[i] * toDistribute / sigmaDelta;
\r
473 columnMinWidths[i] += add;
\r
476 columnMinWidths[columnMinWidths.length - 1] += left;
\r
480 private void averageDeltaToCell(int[] columnMinWidths, int toDistribute) {
\r
482 if (toDistribute <= 0) {
\r
485 ArrayList list = new ArrayList();
\r
486 for (int i = 0; i < columnMinWidths.length; i++) {
\r
487 if (!_tableInfo.getWidthSpecified()[i]) {
\r
488 list.add(new Integer(i));
\r
491 if (list.size() == 0) {
\r
492 for (int i = 0; i < columnMinWidths.length; i++) {
\r
493 list.add(new Integer(i));
\r
496 int padding = toDistribute / list.size();
\r
497 int left = toDistribute % list.size();
\r
498 for (int i = 0, n = list.size(); i < n; i++) {
\r
499 columnMinWidths[((Integer) list.get(i)).intValue()] += padding;
\r
502 for (int i = 0; i < left; i++) {
\r
503 columnMinWidths[((Integer) list.get(i)).intValue()] += 1;
\r
512 private void distributeHeights(int toDistribute, int[] heights) {
\r
513 if (heights.length == 0)
\r
515 int eachDelta = toDistribute / heights.length;
\r
516 for (int i = 0; i < heights.length - 1; i++) {
\r
517 heights[i] += eachDelta;
\r
519 heights[heights.length - 1] += toDistribute - (heights.length - 1)
\r
526 * @return the cell height
\r
528 public int getCellHeight(TableCellInfo cellinfo, int[] heights) {
\r
529 int rowIndex = cellinfo.getRowIndex();
\r
530 int rowspan = cellinfo.getRowSpan();
\r
532 for (int i = 0; i < rowspan; i++) {
\r
533 h += heights[rowIndex + i];
\r
535 h += (rowspan - 1) * _vspacing;
\r
542 * @return the cell width
\r
544 public int getCellWidth(TableCellInfo cellinfo, int[] widths) {
\r
545 int columnIndex = cellinfo.getColumnIndex();
\r
546 int colspan = cellinfo.getColSpan();
\r
548 for (int i = 0; i < colspan; i++) {
\r
549 w += widths[columnIndex + i];
\r
551 w += (colspan - 1) * _hspacing;
\r
559 * number of columns
\r
562 * @param columnWidths
\r
563 * current columns widths. After the adjust, need make sure the
\r
564 * columnWidths to be bigger than desired width
\r
566 private void adjustWidth(int column, int colspan, int width,
\r
567 int[] columnWidths) {
\r
568 adjustSpan(column, colspan, width, columnWidths, _hspacing);
\r
572 * @see #adjustWidth(int, int, int, int[])
\r
574 private void adjustHeight(int rowIndex, int rowspan, int height,
\r
576 adjustSpan(rowIndex, rowspan, height, heights, _vspacing);
\r
579 static private void adjustSpan(int column, int colspan, int width,
\r
580 int[] columnWidths, int spacing) {
\r
582 for (int i = 0; i < colspan; i++) {
\r
583 spanwidth += columnWidths[column + i];
\r
585 // XXX: vspacing here?
\r
586 spanwidth += (colspan - 1) * spacing;
\r
588 if (spanwidth >= width) {
\r
591 int delta = width - spanwidth;
\r
592 int deltaeach = delta / colspan;
\r
593 for (int i = 0; i < colspan - 1; i++) {
\r
594 columnWidths[column + i] += deltaeach;
\r
596 columnWidths[column + colspan - 1] += (delta - (colspan - 1)
\r
601 * @return the row heights
\r
603 public int[] getRowHeights() {
\r
604 return _rowHeights;
\r
608 * @return the column widths
\r
610 public int[] getColumnWidths() {
\r
611 return _columnWidths;
\r
615 * @return the vertical spacing value
\r
617 public int getVSpacing() {
\r
622 * @return the horizontal spacing value
\r
624 public int getHSpacing() {
\r
630 * @return the table row info for the figure
\r
632 public TableRowInfo getRowInfo(CSSFigure figure) {
\r
633 return _tableInfo.findRowInfo(figure);
\r
637 * @return the table caption info
\r
639 public TableCaptionInfo getCaptionInfo() {
\r
640 return _tableInfo.getCaption();
\r
645 * @return the table row group info for the figure
\r
647 public TableRowGroupInfo getGroupInfo(CSSFigure figure) {
\r
648 return _tableInfo.findGroupInfo(figure);
\r
652 * @return the row's x
\r
654 public int getRowX() {
\r
659 * @return the row's width
\r
661 public int getRowWidth() {
\r
668 * @see org.eclipse.jst.pagedesigner.css2.layout.CSSBlockFlowLayout#shouldExpand()
\r
670 public boolean shouldExpand() {
\r
675 * @return the rendered dimensions of the table caption
\r
677 public Dimension getCaptionSize() {
\r
678 return _captionSize;
\r
684 * @see org.eclipse.jst.pagedesigner.css2.layout.ICSSPainter#paintFigure(org.eclipse.draw2d.Graphics)
\r
686 public void paintFigure(Graphics g) {
\r
687 ICSSStyle style = this.getCSSStyle();
\r
688 if (style != null) {
\r
689 ITagEditInfo info = (ITagEditInfo) style
\r
690 .getAdapter(ITagEditInfo.class);
\r
691 if (info != null && info.needTableDecorator()) {
\r
692 List cells = _tableInfo.getCells();
\r
693 for (int i = 0, size = cells.size(); i < size; i++) {
\r
694 TableCellInfo cellInfo = (TableCellInfo) cells.get(i);
\r
695 IFigure cellfigure = cellInfo.getFigure();
\r
696 Rectangle rect = cellfigure.getBounds().getCopy();
\r
697 rect = rect.expand(1, 1);
\r
698 g.setLineStyle(Graphics.LINE_SOLID);
\r
700 g.setForegroundColor(ColorConstants.lightGray);
\r
701 g.drawRectangle(rect);
\r