/*
* Copyright (c) 2007 Matthew Hall 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:
* Matthew Hall - initial API and implementation
*/
package org.eclipse.nebula.paperclips.core.grid.internal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.nebula.paperclips.core.CompositeEntry;
import org.eclipse.nebula.paperclips.core.CompositePiece;
import org.eclipse.nebula.paperclips.core.PaperClips;
import org.eclipse.nebula.paperclips.core.PrintIterator;
import org.eclipse.nebula.paperclips.core.PrintPiece;
import org.eclipse.nebula.paperclips.core.grid.GridCell;
import org.eclipse.nebula.paperclips.core.grid.GridColumn;
import org.eclipse.nebula.paperclips.core.grid.GridLookPainter;
import org.eclipse.nebula.paperclips.core.grid.GridMargins;
import org.eclipse.nebula.paperclips.core.grid.GridPrint;
import org.eclipse.nebula.paperclips.core.internal.util.PaperClipsUtil;
import org.eclipse.nebula.paperclips.core.internal.util.PrintSizeStrategy;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
public class GridIterator implements PrintIterator {
final Device device;
final Point dpi;
final GridColumn[] columns;
final int[][] columnGroups;
final GridLookPainter look;
final GridCellIterator[][] header;
final GridCellIterator[][] body;
final GridCellIterator[][] footer;
final boolean cellClippingEnabled;
final int[] minimumColSizes; // PIXELS
final int[] preferredColSizes; // PIXELS
final Point minimumSize; // PIXELS
final Point preferredSize; // PIXELS
// This is the cursor!
private int row;
// Determines whether top edge of cell border is drawn open or closed for
// current row.
private boolean rowStarted;
public GridIterator(GridPrint grid, Device device, GC gc) {
this.device = device;
this.dpi = device.getDPI();
this.columns = new GridColumn[grid.getColumns().length];
System.arraycopy(grid.getColumns(), 0, this.columns, 0,
grid.getColumns().length);
this.columnGroups = grid.getColumnGroups();
this.header = createGridCellIterators(grid.getHeaderCells(), device, gc);
this.body = createGridCellIterators(grid.getBodyCells(), device, gc);
this.footer = createGridCellIterators(grid.getFooterCells(), device, gc);
this.cellClippingEnabled = grid.isCellClippingEnabled();
this.look = grid.getLook().getPainter(device, gc);
this.minimumColSizes = computeColumnSizes(PrintSizeStrategy.MINIMUM);
this.preferredColSizes = computeColumnSizes(PrintSizeStrategy.PREFERRED);
this.minimumSize = computeSize(PrintSizeStrategy.MINIMUM,
minimumColSizes);
this.preferredSize = computeSize(PrintSizeStrategy.PREFERRED,
preferredColSizes);
row = 0;
rowStarted = false;
}
private static GridCellIterator[][] createGridCellIterators(
GridCell[][] gridCells, Device device, GC gc) {
GridCellIterator[][] result = new GridCellIterator[gridCells.length][];
for (int rowIndex = 0; rowIndex < result.length; rowIndex++)
result[rowIndex] = createRowCellIterators(gridCells[rowIndex],
device, gc);
return result;
}
private static GridCellIterator[] createRowCellIterators(
GridCell[] rowCells, Device device, GC gc) {
GridCellIterator[] result = new GridCellIterator[rowCells.length];
for (int cellIndex = 0; cellIndex < rowCells.length; cellIndex++)
result[cellIndex] = ((GridCellImpl) rowCells[cellIndex]).iterator(
device, gc);
return result;
}
/** Copy constructor (used by copy() only) */
private GridIterator(GridIterator that) {
this.device = that.device;
this.dpi = that.dpi;
this.columns = that.columns;
this.columnGroups = that.columnGroups;
this.header = that.header; // never directly modified, clone not
// necessary
this.body = cloneRows(that.body, that.row); // Only need to deep copy
// the unconsumed rows.
this.footer = that.footer; // never directly modified, clone not
// necessary
this.cellClippingEnabled = that.cellClippingEnabled;
this.look = that.look;
this.minimumColSizes = that.minimumColSizes;
this.preferredColSizes = that.preferredColSizes;
this.minimumSize = that.minimumSize;
this.preferredSize = that.preferredSize;
this.row = that.row;
this.rowStarted = that.rowStarted;
}
private static GridCellIterator[][] cloneRows(GridCellIterator[][] rows,
int firstRow) {
GridCellIterator[][] result = (GridCellIterator[][]) rows.clone();
// Cloning the outer array is all that's necessary. The inner arrays
// (rows) are cloned every time a row
// is laid out, so all we have to do is make sure different
// GridIterators have distinct outer arrays.
// for ( int i = firstRow; i < result.length; i++ )
// result[i] = cloneRow( result[i] );
return result;
}
/**
* Compute the size of a column, respecting the constraints of the
* GridColumn.
*/
private int computeCellWidth(GridCellIterator entry, GridColumn col,
PrintSizeStrategy strategy) {
if (col.size == SWT.DEFAULT)
return strategy.computeSize(entry.getTarget()).x;
if (col.size == GridPrint.PREFERRED)
return entry.getTarget().preferredSize().x;
return Math.round(col.size * device.getDPI().x / 72f);
}
private static boolean isExplicitSize(GridColumn col) {
return col.size > 0;
}
private void applyColumnGrouping(int[] columnSizes) {
for (int groupIndex = 0; groupIndex < columnGroups.length; groupIndex++) {
int[] group = columnGroups[groupIndex];
// find max column width in group
int maxSize = 0;
for (int columnInGroupIndex = 0; columnInGroupIndex < group.length; columnInGroupIndex++) {
int col = group[columnInGroupIndex];
maxSize = Math.max(maxSize, columnSizes[col]);
}
// grow all columns to max column width
for (int columnInGroupIndex = 0; columnInGroupIndex < group.length; columnInGroupIndex++) {
int col = group[columnInGroupIndex];
columnSizes[col] = maxSize;
}
}
}
private boolean isColumnGrouped(int col) {
for (int groupIndex = 0; groupIndex < columnGroups.length; groupIndex++) {
int[] group = columnGroups[groupIndex];
for (int columnInGroupIndex = 0; columnInGroupIndex < group.length; columnInGroupIndex++) {
int groupedColumn = group[columnInGroupIndex];
if (groupedColumn == col)
return true;
}
}
return false;
}
private int[] computeColumnSizes(PrintSizeStrategy strategy) {
final int[] result = new int[columns.length];
final GridCellIterator[][] rows = aggregateHeaderBodyAndFooterCells();
calculateExplicitlySizedColumnWidths(result);
calculateColumnWidthsForCellsSpanningOneColumn(result, rows, strategy);
applyColumnGrouping(result);
calculateColumnWidthsForCellsSpanningMultipleColumns(result, rows,
strategy);
applyColumnGrouping(result);
return result;
}
private GridCellIterator[][] aggregateHeaderBodyAndFooterCells() {
GridCellIterator[][] rows = new GridCellIterator[body.length
+ header.length + footer.length][];
int offset = 0;
System.arraycopy(body, 0, rows, offset, body.length);
offset += body.length;
System.arraycopy(header, 0, rows, offset, header.length);
offset += header.length;
System.arraycopy(footer, 0, rows, offset, footer.length);
return rows;
}
private void calculateColumnWidthsForCellsSpanningMultipleColumns(
final int[] colSizes, final GridCellIterator[][] rows,
final PrintSizeStrategy strategy) {
int horizontalSpacing = look.getMargins().getHorizontalSpacing();
for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
GridCellIterator[] row = rows[rowIndex];
int columnIndex = 0;
for (int cellIndex = 0; cellIndex < row.length; cellIndex++) {
GridCellIterator entry = row[cellIndex];
int colspan = entry.getColspan();
if (colspan > 1) {
int currentWidth = PaperClipsUtil.sum(colSizes,
columnIndex, colspan);
// Subtract column spacing so the weighted distribution of
// extra width stays proportional.
int minimumWidth = strategy.computeSize(entry.getTarget()).x
- horizontalSpacing * (colspan - 1);
if (currentWidth < minimumWidth) {
int extraWidth = minimumWidth - currentWidth;
int[] indices = getExpandableColumnIndices(columnIndex,
colspan);
int totalWidth = PaperClipsUtil.sumByIndex(colSizes,
indices);
if (totalWidth == 0)
resizeColumnsEqually(colSizes, extraWidth, indices);
else
resizeColumnsProportionateToCurrentSizes(colSizes,
indices, extraWidth, totalWidth);
}
}
columnIndex += colspan;
}
}
}
private void resizeColumnsProportionateToCurrentSizes(final int[] colSizes,
final int[] columnIndices, int adjustment, int totalWidth) {
for (int i = 0; i < columnIndices.length && totalWidth != 0
&& adjustment != 0; i++) {
int columnIndex = columnIndices[i];
int addedWidth = (int) ((long) adjustment * colSizes[columnIndex] / totalWidth);
// Adjust extraWidth and totalCurrentWidth for future iterations.
totalWidth -= colSizes[columnIndex];
adjustment -= addedWidth;
// NOW we can add the added width.
colSizes[columnIndex] += addedWidth;
}
}
private void resizeColumnsEqually(final int[] colSizes, int adjustment,
int[] expandableColumns) {
int expandableCols = expandableColumns.length;
for (int expandableColIndex = 0; expandableColIndex < expandableCols; expandableColIndex++) {
int expandableColumn = expandableColumns[expandableColIndex];
int addedWidth = adjustment / expandableCols;
colSizes[expandableColumn] = addedWidth;
adjustment -= addedWidth;
expandableCols--;
}
}
private void calculateColumnWidthsForCellsSpanningOneColumn(int[] colSizes,
GridCellIterator[][] rows, PrintSizeStrategy strategy) {
for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
GridCellIterator[] row = rows[rowIndex];
int col = 0;
for (int cellIndex = 0; cellIndex < row.length; cellIndex++) {
GridCellIterator entry = row[cellIndex];
// ignore explicitly sized cols
if (entry.getColspan() == 1 && !isExplicitSize(columns[col])) {
colSizes[col] = Math.max(colSizes[col],
computeCellWidth(entry, columns[col], strategy));
}
col += entry.getColspan();
}
}
}
private void calculateExplicitlySizedColumnWidths(int[] colSizes) {
for (int col = 0; col < columns.length; col++)
if (isExplicitSize(columns[col]))
colSizes[col] = Math.round(columns[col].size * dpi.x / 72f);
}
private int[] getExpandableColumnIndices(int firstColumn, int colspan) {
Condition[] conditions = getExpandableColumnConditions();
for (int i = 0; i < conditions.length; i++) {
int[] columns = findColumns(firstColumn, colspan, conditions[i]);
if (columns != null && columns.length > 0)
return columns;
}
return new int[0];
}
private interface Condition {
/**
* Returns whether the column at the specified index satisfies the
* condition.
*
* @param col
* the index of the column to test.
* @return whether the column at the specified index satisfies the
* condition.
*/
boolean satisfiedBy(int col);
}
private Condition[] getExpandableColumnConditions() {
return new Condition[] { new Condition() {
public boolean satisfiedBy(int col) {
// Ungrouped columns with nonzero weight are first choice for
// expansion.
return !isColumnGrouped(col) && columns[col].weight > 0;
}
},
new Condition() {
public boolean satisfiedBy(int col) {
// Grouped columns with nonzero weight are next choice
return isColumnGrouped(col) && columns[col].weight > 0;
}
},
new Condition() {
public boolean satisfiedBy(int col) {
// Ungrouped columns with GridPrint.PREFERRED size are next
// choice.
return !isColumnGrouped(col)
&& columns[col].size == GridPrint.PREFERRED;
}
},
new Condition() {
public boolean satisfiedBy(int col) {
// Grouped columns with GridPrint.PREFERRED size are next
// choice.
return isColumnGrouped(col)
&& columns[col].size == GridPrint.PREFERRED;
}
},
new Condition() {
public boolean satisfiedBy(int col) {
// Ungrouped columns with SWT.DEFAULT size are next choice.
return !isColumnGrouped(col)
&& columns[col].size == SWT.DEFAULT;
}
},
new Condition() {
public boolean satisfiedBy(int col) {
// Grouped columns with SWT.DEFAULT size are last choice.
return isColumnGrouped(col) && columns[col].size == SWT.DEFAULT;
}
} };
}
private int[] findColumns(Condition condition) {
return findColumns(0, columns.length, condition);
}
private int[] findColumns(int start, int count, Condition condition) {
int[] resultTemp = null;
int matches = 0;
final int end = start + count;
for (int index = start; index < end; index++)
if (condition.satisfiedBy(index)) {
if (resultTemp == null)
resultTemp = new int[count];
resultTemp[matches++] = index;
}
if (matches == 0)
return new int[0];
int[] result = new int[matches];
System.arraycopy(resultTemp, 0, result, 0, matches);
return result;
}
private Point computeSize(PrintSizeStrategy strategy, int[] colSizes) {
final GridMargins margins = look.getMargins();
int width = computeMarginWidth() + PaperClipsUtil.sum(colSizes);
int height = 0;
// This algorithm is not strictly accurate but probably good enough. The
// header and footer row heights
// are being calculated using getMinimumSize() and getPreferredSize(),
// which do not necessarily return
// the total content height.
if (header.length > 0)
height += computeHeaderHeight(margins, strategy);
else
height += Math.max(margins.getBodyTop(false, true),
margins.getBodyTop(false, false));
height += computeMaxBodyRowHeight(strategy);
if (footer.length > 0)
height += computeFooterHeight(strategy, margins);
else
height += Math.max(margins.getBodyBottom(false, false),
margins.getBodyBottom(false, true));
return new Point(width, height);
}
private int computeHeaderHeight(final GridMargins margins,
PrintSizeStrategy strategy) {
int headerHeight = margins.getHeaderTop()
+ margins.getHeaderVerticalSpacing()
* (header.length - 1)
+ Math.max(margins.getBodyTop(true, true),
margins.getBodyTop(true, false));
for (int rowIndex = 0; rowIndex < header.length; rowIndex++) {
GridCellIterator[] row = header[rowIndex];
int col = 0;
int rowHeight = 0;
for (int cellIndex = 0; cellIndex < row.length; cellIndex++) {
GridCellIterator entry = row[cellIndex];
// Find tallest cell in row.
rowHeight = Math.max(rowHeight,
strategy.computeSize(entry.getTarget()).y);
col += entry.getColspan();
}
headerHeight += rowHeight;
}
return headerHeight;
}
private int computeMaxBodyRowHeight(PrintSizeStrategy strategy) {
int maxBodyRowHeight = 0;
for (int rowIndex = 0; rowIndex < body.length; rowIndex++) {
GridCellIterator[] row = body[rowIndex];
int col = 0;
for (int cellIndex = 0; cellIndex < row.length; cellIndex++) {
GridCellIterator entry = row[cellIndex];
// Find the greatest height of all cells' calculated sizes.
maxBodyRowHeight = Math.max(maxBodyRowHeight,
strategy.computeSize(entry.getTarget()).y);
col += entry.getColspan();
}
}
return maxBodyRowHeight;
}
private int computeFooterHeight(PrintSizeStrategy strategy,
final GridMargins margins) {
int footerHeight = Math.max(margins.getBodyBottom(true, false),
margins.getBodyBottom(true, true))
+ margins.getFooterVerticalSpacing()
* (footer.length - 1)
+ margins.getFooterBottom();
for (int rowIndex = 0; rowIndex < footer.length; rowIndex++) {
GridCellIterator[] row = footer[rowIndex];
int col = 0;
int rowHeight = 0;
for (int cellIndex = 0; cellIndex < row.length; cellIndex++) {
GridCellIterator entry = row[cellIndex];
// Find tallest cell in row.
rowHeight = Math.max(rowHeight,
strategy.computeSize(entry.getTarget()).y);
col += entry.getColspan();
}
footerHeight += rowHeight;
}
return footerHeight;
}
public Point minimumSize() {
return new Point(minimumSize.x, minimumSize.y);
}
public Point preferredSize() {
return new Point(preferredSize.x, preferredSize.y);
}
private Condition[] getShrinkableColumnConditions() {
/*
* Disabled: new Condition() { public boolean satisfiedBy( int col ) {
* // Search first for columns with DEFAULT size. return
* columns[col].size == SWT.DEFAULT; } },
*/
return new Condition[] { new Condition() {
public boolean satisfiedBy(int col) {
// Search next for columns with DEFAULT or PREFERRED size.
int size = columns[col].size;
return size == SWT.DEFAULT || size == GridPrint.PREFERRED;
}
} };
}
private int[] findShrinkableColumns(int extraWidth) {
Condition[] conditions = getShrinkableColumnConditions();
for (int i = 0; i < conditions.length; i++) {
int[] indices = findColumns(conditions[i]);
if (PaperClipsUtil.sumByIndex(minimumColSizes, indices) >= extraWidth)
return indices;
}
return findAllColumns();
}
private int[] findAllColumns() {
int[] result = new int[columns.length];
for (int i = 0; i < result.length; i++)
result[i] = i;
return result;
}
private int[] computeColumnWidths(int width) {
int minimumWidth = PaperClipsUtil.sum(minimumColSizes);
int preferredWidth = PaperClipsUtil.sum(preferredColSizes);
if (width < minimumWidth)
return reduceMinimumColumnWidths(minimumWidth - width);
else if (width == minimumWidth)
return minimumColSizes;
else if (width < preferredWidth)
return expandMinimumColumnWidths(width - minimumWidth);
else if (preferredWidth == width)
return preferredColSizes;
else
// ( preferredWidth < width )
return expandPreferredColumnWidthsByWeight(width - preferredWidth);
}
private int[] expandPreferredColumnWidthsByWeight(int extraWidth) {
int[] weightedCols = findColumns(new Condition() {
public boolean satisfiedBy(int col) {
return columns[col].weight > 0;
}
});
int totalWeight = 0;
for (int i = 0; i < weightedCols.length; i++)
totalWeight += columns[weightedCols[i]].weight;
int[] colSizes = PaperClipsUtil.copy(preferredColSizes);
for (int weightedColIndex = 0; weightedColIndex < weightedCols.length; weightedColIndex++) {
int columnIndex = weightedCols[weightedColIndex];
int columnWeight = columns[columnIndex].weight;
int addWidth = (int) ((long) extraWidth * columnWeight / totalWeight);
colSizes[columnIndex] += addWidth;
// adjust extraWidth and totalWeight - eliminates round-off error
extraWidth -= addWidth;
totalWeight -= columnWeight;
}
return colSizes;
}
private int[] expandMinimumColumnWidths(int expansion) {
int difference = PaperClipsUtil.sum(preferredColSizes)
- PaperClipsUtil.sum(minimumColSizes);
int[] colSizes = PaperClipsUtil.copy(minimumColSizes);
for (int i = 0; i < columns.length && difference != 0 && expansion != 0; i++) {
int columnDifference = preferredColSizes[i] - minimumColSizes[i];
int change = (int) ((long) expansion * columnDifference / difference);
colSizes[i] += change;
// adjust extraWidth and difference - eliminates round-off error
expansion -= change;
difference -= columnDifference;
}
return colSizes;
}
private int computeMarginWidth() {
GridMargins margins = look.getMargins();
return margins.getLeft() + margins.getRight()
+ margins.getHorizontalSpacing() * (columns.length - 1);
}
private int[] reduceMinimumColumnWidths(int reduction) {
int[] colSizes = PaperClipsUtil.copy(minimumColSizes);
int[] shrinkableCols = findShrinkableColumns(reduction);
int shrinkableWidth = PaperClipsUtil.sumByIndex(colSizes,
shrinkableCols);
for (int i = 0; i < shrinkableCols.length && shrinkableWidth != 0
&& reduction != 0; i++) {
int col = shrinkableCols[i];
int columnReduction = (int) ((long) colSizes[col] * reduction / shrinkableWidth);
shrinkableWidth -= colSizes[col];
colSizes[col] -= columnReduction;
reduction -= columnReduction;
}
return colSizes;
}
public boolean hasNext() {
return row < body.length;
}
private PrintPiece nextRow(final GridCellIterator[] cells,
final int[] columnWidths, final int height, final boolean bottomOpen) {
if (bottomOpen && rowContainsNonDefaultVertAlignment(cells))
return null;
if (height < 0)
return null;
final int[] cellWidths = calculateCellWidths(cells, columnWidths);
PrintPiece[] pieces = layoutCellsWithNonFillVertAlignment(cells,
height, bottomOpen, cellWidths);
if (pieces == null)
return null;
final int rowHeight = calculateRowHeight(pieces, cells);
pieces = layoutCellsWithFillVertAlignment(cells, rowHeight, cellWidths,
pieces);
if (pieces == null)
return null;
final int[] xOffsets = new int[cells.length];
final int[] yOffsets = new int[cells.length];
applyCellAlignment(cells, cellWidths, pieces, rowHeight, xOffsets,
yOffsets);
return createRowResult(pieces, xOffsets, yOffsets);
}
private static boolean rowContainsNonDefaultVertAlignment(
final GridCellIterator[] cells) {
for (int i = 0; i < cells.length; i++)
if (!isDefaultVerticalAlignment(cells[i].getVerticalAlignment()))
return true;
return false;
}
private static boolean isDefaultVerticalAlignment(int vAlignment) {
return vAlignment == SWT.DEFAULT || vAlignment == SWT.TOP;
}
private int[] calculateCellWidths(final GridCellIterator[] cells,
final int[] columnWidths) {
final int[] result = new int[cells.length];
final int horzSpacing = look.getMargins().getHorizontalSpacing();
int col = 0;
for (int cellIndex = 0; cellIndex < cells.length; cellIndex++) {
int colspan = cells[cellIndex].getColspan();
result[cellIndex] = (colspan - 1) * horzSpacing
+ PaperClipsUtil.sum(columnWidths, col, colspan);
col += colspan;
}
return result;
}
private static PrintPiece[] layoutCellsWithNonFillVertAlignment(
final GridCellIterator[] cells, final int height,
final boolean bottomOpen, final int[] cellWidths) {
final PrintPiece[] pieces = new PrintPiece[cells.length];
for (int cellIndex = 0; cellIndex < cells.length; cellIndex++) {
final GridCellIterator cell = cells[cellIndex];
final PrintIterator iter = cell.getTarget();
final int cellWidth = cellWidths[cellIndex];
if (iter.hasNext() && cell.getVerticalAlignment() != SWT.FILL) {
PrintPiece piece = pieces[cellIndex] = PaperClips.next(iter,
cellWidth, height);
if ((piece == null) || (iter.hasNext() && !bottomOpen)) {
PaperClipsUtil.dispose(piece, pieces);
return null;
}
}
}
return pieces;
}
private static int calculateRowHeight(final PrintPiece[] cellPieces,
final GridCellIterator[] cells) {
int maxHeight = 0;
for (int cellIndex = 0; cellIndex < cells.length; cellIndex++) {
GridCellIterator cell = cells[cellIndex];
if (cell.getVerticalAlignment() == SWT.FILL)
maxHeight = Math.max(maxHeight,
cell.getTarget().minimumSize().y);
else if (cellPieces[cellIndex] != null)
maxHeight = Math.max(maxHeight,
cellPieces[cellIndex].getSize().y);
}
return maxHeight;
}
private static PrintPiece[] layoutCellsWithFillVertAlignment(
final GridCellIterator[] cells, final int height,
final int[] cellWidths, final PrintPiece[] cellPieces) {
for (int cellIndex = 0; cellIndex < cells.length; cellIndex++) {
GridCellIterator cell = cells[cellIndex];
PrintIterator iter = cell.getTarget();
if (cell.getVerticalAlignment() == SWT.FILL) {
PrintPiece piece = cellPieces[cellIndex] = PaperClips.next(
iter, cellWidths[cellIndex], height);
if (piece == null || iter.hasNext()) {
PaperClipsUtil.dispose(piece, cellPieces);
return null;
}
}
}
return cellPieces;
}
private void applyCellAlignment(final GridCellIterator[] cells,
final int[] cellWidths, final PrintPiece[] pieces,
final int rowHeight, final int[] xOffsets, final int[] yOffsets) {
final int horzSpacing = look.getMargins().getHorizontalSpacing();
int x = 0;
int col = 0;
for (int cellIndex = 0; cellIndex < cells.length; cellIndex++) {
xOffsets[cellIndex] = x;
yOffsets[cellIndex] = 0;
GridCellIterator cell = cells[cellIndex];
PrintPiece piece = pieces[cellIndex];
if (piece != null) {
Point size = piece.getSize();
int hAlignment = resolveHorzAlignment(
cell.getHorizontalAlignment(), columns[col].align);
xOffsets[cellIndex] += getHorzAlignmentOffset(hAlignment,
size.x, cellWidths[cellIndex]);
yOffsets[cellIndex] += getVertAlignmentOffset(
cell.getVerticalAlignment(), size.y, rowHeight);
}
x += cellWidths[cellIndex] + horzSpacing;
col += cell.getColspan();
}
}
private static int resolveHorzAlignment(int cellAlignment,
int columnAlignment) {
return cellAlignment == SWT.DEFAULT ? columnAlignment : cellAlignment;
}
private static int getHorzAlignmentOffset(int alignment, int pieceWidth,
int totalWidth) {
if (alignment == SWT.CENTER)
return (totalWidth - pieceWidth) / 2;
else if (alignment == SWT.RIGHT)
return totalWidth - pieceWidth;
return 0;
}
private static int getVertAlignmentOffset(final int alignment,
final int pieceHeight, final int cellHeight) {
int offset = 0;
if (alignment == SWT.CENTER) {
offset = (cellHeight - pieceHeight) / 2;
} else if (alignment == SWT.BOTTOM) {
offset = cellHeight - pieceHeight;
}
return offset;
}
private static PrintPiece createRowResult(final PrintPiece[] pieces,
final int[] xOffsets, final int[] yOffsets) {
List result = new ArrayList();
for (int cellIndex = 0; cellIndex < pieces.length; cellIndex++)
if (pieces[cellIndex] != null)
result.add(new CompositeEntry(pieces[cellIndex], new Point(
xOffsets[cellIndex], yOffsets[cellIndex])));
return new CompositePiece(result);
}
private static boolean hasNext(GridCellIterator[] cells) {
for (int i = 0; i < cells.length; i++)
if (cells[i].getTarget().hasNext())
return true;
return false;
}
public PrintPiece next(final int width, int height) {
if (!hasNext())
PaperClips.error(SWT.ERROR_UNSPECIFIED, "No more content"); //$NON-NLS-1$
GridMargins margins = look.getMargins();
int[] colSizes = computeColumnWidths(width - computeMarginWidth());
final boolean headerPresent = header.length > 0;
final int[] headerHeights = new int[header.length];
final int[][] headerColSpans = new int[header.length][];
PrintPiece headerPiece = null;
if (headerPresent) {
height -= margins.getHeaderTop();
headerPiece = nextHeaderPiece(colSizes, height, headerHeights,
headerColSpans);
if (headerPiece == null)
return null;
height -= headerPiece.getSize().y;
}
final boolean footerPresent = footer.length > 0;
final int[] footerHeights = new int[footer.length];
final int[][] footerColSpans = new int[footer.length][];
PrintPiece footerPiece = null;
if (footerPresent) {
height -= margins.getFooterBottom();
footerPiece = nextFooterPiece(colSizes, height, footerHeights,
footerColSpans);
if (footerPiece == null) {
PaperClipsUtil.dispose(headerPiece);
return null;
}
height -= footerPiece.getSize().y;
}
final int firstRow = row;
final boolean topOpen = rowStarted;
final List bodyRows = new ArrayList();
final List bodyColSpans = new ArrayList();
height -= margins.getBodyTop(headerPresent, topOpen);
final PrintPiece bodyPiece = nextBodyPiece(colSizes, height, bodyRows,
bodyColSpans, footerPresent);
if (bodyPiece == null)
return null;
final boolean bottomOpen = rowStarted;
return createResult(colSizes, headerPiece, headerHeights,
headerColSpans, firstRow, topOpen, bodyPiece,
PaperClipsUtil.toIntArray(bodyRows),
PaperClipsUtil.toIntIntArray(bodyColSpans), bottomOpen,
footerPiece, footerHeights, footerColSpans);
}
private PrintPiece nextHeaderPiece(final int[] colSizes, final int height,
final int[] rowHeights, final int[][] colSpans) {
return nextHeaderOrFooterPiece(colSizes, height, rowHeights, colSpans,
look.getMargins().getHeaderVerticalSpacing(), header);
}
private PrintPiece nextFooterPiece(final int[] colSizes, final int height,
final int[] rowHeights, final int[][] colSpans) {
return nextHeaderOrFooterPiece(colSizes, height, rowHeights, colSpans,
look.getMargins().getFooterVerticalSpacing(), footer);
}
private PrintPiece nextHeaderOrFooterPiece(final int[] colSizes,
final int height, final int[] rowHeights, final int[][] colSpans,
final int rowSpacing, GridCellIterator[][] headerOrFooter) {
int y = 0;
List entries = new ArrayList();
for (int rowIndex = 0; rowIndex < headerOrFooter.length; rowIndex++) {
GridCellIterator[] row = cloneRow(headerOrFooter[rowIndex]);
colSpans[rowIndex] = new int[row.length];
for (int cellIndex = 0; cellIndex < row.length; cellIndex++)
colSpans[rowIndex][cellIndex] = row[cellIndex].getColspan();
PrintPiece rowPiece = nextRow(row, colSizes, height - y, false);
boolean hasNext = hasNext(row);
if (rowPiece == null || hasNext) {
PaperClipsUtil.dispose(rowPiece);
for (Iterator iter = entries.iterator(); iter.hasNext();) {
CompositeEntry entry = (CompositeEntry) iter.next();
entry.dispose();
}
return null;
}
int rowHeight = rowHeights[rowIndex] = rowPiece.getSize().y;
entries.add(new CompositeEntry(rowPiece, new Point(0, y)));
y += rowHeight + rowSpacing;
}
return new CompositePiece(entries);
}
private PrintPiece nextBodyPiece(int[] colSizes, final int height,
final List rowHeights, final List colSpans,
final boolean footerPresent) {
final GridMargins margins = look.getMargins();
final int rowSpacing = margins.getBodyVerticalSpacing();
final int bodyBottomSpacingOpen = margins.getBodyBottom(footerPresent,
true);
final int bodyBottomSpacingClosed = margins.getBodyBottom(
footerPresent, false);
int y = 0;
List entries = new ArrayList();
while (hasNext()) {
GridCellIterator[] thisRow = cloneRow(body[row]);
PrintPiece rowPiece = nextRow(thisRow, colSizes, height - y
- bodyBottomSpacingClosed, rowStarted);
boolean hasNext = hasNext(thisRow);
if ((cellClippingEnabled || entries.isEmpty())
&& (rowPiece == null || hasNext)) {
thisRow = cloneRow(body[row]);
rowPiece = nextRow(thisRow, colSizes, height - y
- bodyBottomSpacingOpen, true);
hasNext = true;
}
if (rowPiece == null)
break;
entries.add(new CompositeEntry(rowPiece, new Point(0, y)));
body[row] = thisRow;
final int[] rowColSpans = new int[thisRow.length];
for (int cellIndex = 0; cellIndex < rowColSpans.length; cellIndex++)
rowColSpans[cellIndex] = thisRow[cellIndex].getColspan();
colSpans.add(rowColSpans);
final int rowHeight = rowPiece.getSize().y;
rowHeights.add(new Integer(rowHeight));
rowStarted = hasNext;
if (hasNext)
break;
y += rowHeight + rowSpacing;
row++;
}
if (entries.isEmpty())
return null;
return new CompositePiece(entries);
}
private static GridCellIterator[] cloneRow(GridCellIterator[] row) {
GridCellIterator[] result = (GridCellIterator[]) row.clone();
for (int i = 0; i < result.length; i++)
result[i] = result[i].copy();
return result;
}
private PrintPiece createResult(final int[] colSizes,
final PrintPiece headerPiece, final int[] headerRows,
final int[][] headerColSpans, final int firstRow,
final boolean topOpen, final PrintPiece bodyPiece,
final int[] bodyRows, final int[][] bodyColSpans,
final boolean bottomOpen, final PrintPiece footerPiece,
final int[] footerRows, final int[][] footerColSpans) {
if (bodyPiece == null) {
if (headerPiece != null)
headerPiece.dispose();
if (footerPiece != null)
footerPiece.dispose();
return null;
}
List sections = new ArrayList();
PrintPiece lookPiece = new GridLookPainterPiece(look, colSizes,
headerRows, headerColSpans, firstRow, topOpen, bodyRows,
bodyColSpans, bottomOpen, footerRows, footerColSpans);
sections.add(new CompositeEntry(lookPiece, new Point(0, 0)));
GridMargins margins = look.getMargins();
final int x = margins.getLeft();
int y = 0;
if (headerPiece != null) {
y = margins.getHeaderTop();
sections.add(new CompositeEntry(headerPiece, new Point(x, y)));
y += headerPiece.getSize().y;
}
y += margins.getBodyTop(headerPiece != null, topOpen);
sections.add(new CompositeEntry(bodyPiece, new Point(x, y)));
y += bodyPiece.getSize().y
+ margins.getBodyBottom(footerPiece != null, bottomOpen);
if (footerPiece != null)
sections.add(new CompositeEntry(footerPiece, new Point(x, y)));
return new CompositePiece(sections);
}
public PrintIterator copy() {
return new GridIterator(this);
}
}