/*
 * Copyright (c) 2005 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 net.sf.paperclips;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.sf.paperclips.internal.PaperClipsUtil;
import net.sf.paperclips.internal.Util;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.RGB;

/**
 * A Print which arranges child prints into a grid. A grid is initialized with a
 * series of GridColumns, and child prints are laid out into those columns by
 * invoking the add(...) methods.
 * <p>
 * GridPrint uses a column sizing algorithm based on the <a
 * href=http://www.w3.org/TR/html4/appendix/notes.html#h-B.5.2.2>W3C
 * recommendation</a> for automatic layout of tables. GridPrint deviates from
 * the recommendation on one important point: if there is less width available
 * on the print device than the calculated "minimum" size of the grid, the
 * columns will be scaled down to <em>less</em> than their calculated minimum
 * widths. Only when one of the columns goes below its "absolute minimum" will
 * the grid fail to print ( {@link PrintIterator#next(int, int)} returns null).
 * <p>
 * GridPrint offers three basic methods of specifying column size.
 * <ol>
 * <li>Default size. The column will be somewhere between it's minimum and
 * preferred width. GridPrint will determine the optimum widths for all default
 * size columns, using the modified W3C recommendation described above. This is
 * the recommended option for most cases.
 * <li>Preferred size. The column will be sized to it's preferred width. This
 * option is sometimes appropriate, for example when certain portions of text
 * should not be allowed to line-wrap. In cases where only a few cells in a
 * column need to be prevented from line wrapping, consider wrapping them in a
 * NoBreakPrint instead.
 * <li>Explicit size. The column will be the size you specify, expressed in
 * points. 72 points = 1".
 * </ol>
 * Example: GridPrint grid = new GridPrint("d, p, 72pts");
 * <p>
 * In addition, any column can be given a grow attribute. In the event a grid is
 * not as wide as the page, those columns with the grow attribute set will be
 * widened to fill the extra space.
 * <p>
 * Because GridPrint scales columns according to their minimum sizes in the
 * worst-case scenario, the absolute minimum size of a GridPrint is dependant on
 * its child prints and is not clearly defined.
 * <p>
 * If a grid has one of more columns with the grow attribute set, the grid is
 * horizontally greedy. Greedy prints take up all the available space on the
 * page.
 * 
 * @author Matthew Hall
 * @see GridColumn
 * @see PrintIterator#minimumSize()
 * @see PrintIterator#preferredSize()
 */
public final class GridPrint implements Print {
	/**
	 * Constant colspan value indicating that all remaining columns in the row
	 * should be used.
	 */
	public static final int REMAINDER = -1;

	/**
	 * Constant column size value indicating that the column should be given its
	 * preferred size. (In the context of W3C's autolayout recommendation, this
	 * has the effect of setting the columns minimum width to its preferred
	 * width. This value is used in the GridColumn constructor.
	 */
	public static final int PREFERRED = 0;

	/**
	 * Constant cell spacing value indicating that the borders of adjacent cells
	 * should overlap.
	 */
	public static final int BORDER_OVERLAP = -1;

	private final DefaultGridLook defaultLook;

	private GridLook look;

	/** The columns for this grid. */
	final List columns; // List<GridColumn>

	/** Array of column groups. */
	int[][] columnGroups = new int[0][];

	/**
	 * Two-dimension list of all header cells. Each element of this list
	 * represents a row in the header. Each element of a row represents a
	 * cellspan in that row.
	 */
	final List header = new ArrayList(); // List <List <GridCell>>

	/** Column cursor - the column that the next added header cell will go into. */
	private int headerCol = 0;

	/**
	 * Two-dimensional list of all body cells. Each element of this list
	 * represents a row in the body. Each element of a row represents a cellspan
	 * in that row.
	 */

	final List body = new ArrayList(); // List <List <GridCell>>

	/** Column cursor - the column that the next added print will go into. */
	private int bodyCol = 0;

	boolean cellClippingEnabled = true;

	/**
	 * Two-dimension list of all footer cells. Each element of this list
	 * represents a row in the footer. Each element of a row represents a
	 * cellspan in that row.
	 */
	// List <List <GridCell>>
	final List footer = new ArrayList();

	/** Column cursor - the column that the next added footer cell will go into. */
	private int footerCol = 0;

	/**
	 * Constructs a GridPrint with no columns and a default look.
	 */
	public GridPrint() {
		this(new GridColumn[0]);
	}

	/**
	 * Constructs a GridPrint with no columns and the given look.
	 * 
	 * @param look
	 *            the look to apply to the constructed grid.
	 */
	public GridPrint(GridLook look) {
		this(new GridColumn[0], look);
	}

	/**
	 * Constructs a GridPrint with the given columns and a default look.
	 * 
	 * @param columns
	 *            a comma-separated list of parseable column specs.
	 * @see GridColumn#parse(String)
	 */
	public GridPrint(String columns) {
		this(parseColumns(columns));
	}

	/**
	 * Constructs a GridPrint with the given columns and look.
	 * 
	 * @param columns
	 *            a comma-separated list of parseable column specs.
	 * @param look
	 *            the look to apply to the constructed grid.
	 * @see GridColumn#parse(String)
	 */
	public GridPrint(String columns, GridLook look) {
		this(parseColumns(columns), look);
	}

	/**
	 * Constructs a GridPrint with the given columns and spacing.
	 * 
	 * @param columns
	 *            a comma-separated list of column specs.
	 * @param spacing
	 *            the spacing (in points) between grid cells.
	 * @see #BORDER_OVERLAP
	 * @deprecated use GridPrint(String) instead, then set a DefaultGridLook on
	 *             the grid with the desired cell spacing.
	 */
	public GridPrint(String columns, int spacing) {
		this(parseColumns(columns), spacing, spacing);
	}

	/**
	 * Constructs a GridPrint with the given columns and spacing.
	 * 
	 * @param columns
	 *            a comma-separated list of column specs.
	 * @param horizontalSpacing
	 *            the horizontal spacing (in points) between grid cells.
	 * @param verticalSpacing
	 *            the vertical spacing (in points) between grid cells.
	 * @see GridColumn#parse(String)
	 * @see #BORDER_OVERLAP
	 * @deprecated use GridPrint(String) instead, then set a DefaultGridLook on
	 *             the grid with the desired cell spacing.
	 */
	public GridPrint(String columns, int horizontalSpacing, int verticalSpacing) {
		this(parseColumns(columns), horizontalSpacing, verticalSpacing);
	}

	/**
	 * Constructs a GridPrint with the given columns and a default look.
	 * 
	 * @param columns
	 *            the columns for the new grid.
	 */
	public GridPrint(GridColumn[] columns) {
		Util.noNulls(columns);

		this.columns = new ArrayList();
		for (int i = 0; i < columns.length; i++)
			this.columns.add(columns[i]);
		this.look = defaultLook = new DefaultGridLook();
	}

	/**
	 * Constructs a GridPrint with the given columns and look.
	 * 
	 * @param columns
	 *            the columns for the new grid.
	 * @param look
	 *            the look to apply to the constructed grid.
	 */
	public GridPrint(GridColumn[] columns, GridLook look) {
		this(columns);
		setLook(look);
	}

	/**
	 * Constructs a GridPrint with the given columns and spacing.
	 * 
	 * @param columns
	 *            the columns for the new grid.
	 * @param spacing
	 *            the spacing (in points) between grid cells.
	 * @see #BORDER_OVERLAP
	 * @deprecated use GridPrint(GridColumn[]) instead, then set a
	 *             DefaultGridLook on the grid with the desired cell spacing.
	 */
	public GridPrint(GridColumn[] columns, int spacing) {
		this(columns, spacing, spacing);
	}

	/**
	 * Construct a GridPrint with the given columns and spacing.
	 * 
	 * @param columns
	 *            the columns for the new grid.
	 * @param horizontalSpacing
	 *            the horizontal spacing (in points) between grid cells.
	 * @param verticalSpacing
	 *            the vertical spacing (in points) between grid cells.
	 * @see #BORDER_OVERLAP
	 * @deprecated use GridPrint(GridColumn[]) instead, then set a
	 *             DefaultGridLook on the grid with the desired cell spacing.
	 */
	public GridPrint(GridColumn[] columns, int horizontalSpacing,
			int verticalSpacing) {
		this(columns);
		defaultLook.setCellSpacing(horizontalSpacing, verticalSpacing);
	}

	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((body == null) ? 0 : body.hashCode());
		result = prime * result + bodyCol;
		result = prime * result + (cellClippingEnabled ? 1231 : 1237);
		result = prime * result + GridPrint.hashCode(columnGroups);
		result = prime * result + ((columns == null) ? 0 : columns.hashCode());
		result = prime * result
				+ ((defaultLook == null) ? 0 : defaultLook.hashCode());
		result = prime * result + ((footer == null) ? 0 : footer.hashCode());
		result = prime * result + footerCol;
		result = prime * result + ((header == null) ? 0 : header.hashCode());
		result = prime * result + headerCol;
		result = prime * result + ((look == null) ? 0 : look.hashCode());
		return result;
	}

	private static int hashCode(int[][] array) {
		int prime = 31;
		if (array == null)
			return 0;
		int result = 1;
		for (int index = 0; index < array.length; index++) {
			result = prime * result
					+ (array[index] == null ? 0 : hashCode(array[index]));
		}
		return result;
	}

	private static int hashCode(int[] array) {
		int prime = 31;
		if (array == null)
			return 0;
		int result = 1;
		for (int index = 0; index < array.length; index++) {
			result = prime * result + array[index];
		}
		return result;
	}

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		GridPrint other = (GridPrint) obj;
		if (body == null) {
			if (other.body != null)
				return false;
		} else if (!body.equals(other.body))
			return false;
		if (bodyCol != other.bodyCol)
			return false;
		if (cellClippingEnabled != other.cellClippingEnabled)
			return false;
		if (!Util.equal(columnGroups, other.columnGroups))
			return false;
		if (columns == null) {
			if (other.columns != null)
				return false;
		} else if (!columns.equals(other.columns))
			return false;
		if (defaultLook == null) {
			if (other.defaultLook != null)
				return false;
		} else if (!defaultLook.equals(other.defaultLook))
			return false;
		if (footer == null) {
			if (other.footer != null)
				return false;
		} else if (!footer.equals(other.footer))
			return false;
		if (footerCol != other.footerCol)
			return false;
		if (header == null) {
			if (other.header != null)
				return false;
		} else if (!header.equals(other.header))
			return false;
		if (headerCol != other.headerCol)
			return false;
		if (look == null) {
			if (other.look != null)
				return false;
		} else if (!look.equals(other.look))
			return false;
		return true;
	}

	/**
	 * Adds the column on the right edge of the grid. Any cells which have been
	 * added to the grid prior to adding the column will be adjusted as follows:
	 * the right-hand cell of each completed row will have it's colspan expanded
	 * to fill the added column.
	 * 
	 * @param column
	 *            the column to add to the grid.
	 * @see GridColumn#parse(String)
	 */
	public void addColumn(String column) {
		addColumn(columns.size(), GridColumn.parse(column));
	}

	/**
	 * Adds the column on the right edge of the grid. Any cells which have been
	 * added to the grid prior to adding the column will be adjusted as follows:
	 * the right-hand cell of each completed row will have it's colspan expanded
	 * to fill the added column.
	 * 
	 * @param column
	 *            the column to add to the grid.
	 */
	public void addColumn(GridColumn column) {
		addColumn(columns.size(), column);
	}

	/**
	 * Inserts the column at the specified position in the grid. Any cells which
	 * have been added to the grid prior to adding the column will be adjusted
	 * as follows: on each row, the cell which overlaps or whose right edge
	 * touches the insert position will be expanded to fill the added column.
	 * 
	 * @param index
	 *            the insert position.
	 * @param column
	 *            the column to be inserted.
	 * @see GridColumn#parse(String)
	 */
	public void addColumn(int index, String column) {
		addColumn(index, GridColumn.parse(column));
	}

	/**
	 * Inserts the column at the specified position in the grid. Any cells which
	 * have been added to the grid prior to adding the column will be adjusted
	 * as follows: on each row, the cell which overlaps or whose right edge
	 * touches the insert position will be expanded to fill the added column.
	 * 
	 * @param index
	 *            the insert position.
	 * @param column
	 *            the column to be inserted.
	 */
	public void addColumn(int index, GridColumn column) {
		checkColumnInsert(index);
		Util.notNull(column);

		this.columns.add(index, column);
		adjustForColumnInsert(index, 1);
	}

	/**
	 * Adds the columns on the right edge of the grid. Any cells which have been
	 * added to the grid prior to adding the columns will be adjusted as
	 * follows: the right-hand cell of each completed row will have it's colspan
	 * expanded to fill the added columns.
	 * 
	 * @param columns
	 *            the columns to add to the grid.
	 * @see GridColumn#parse(String)
	 */
	public void addColumns(String columns) {
		addColumns(this.columns.size(), parseColumns(columns));
	}

	/**
	 * Adds the columns on the right edge of the grid. Any cells which have been
	 * added to the grid prior to adding the columns will be adjusted as
	 * follows: the right-hand cell of each completed row will have it's colspan
	 * expanded to fill the added columns.
	 * 
	 * @param columns
	 *            the columns to add to the grid.
	 */
	public void addColumns(GridColumn[] columns) {
		addColumns(this.columns.size(), columns);
	}

	/**
	 * Inserts the columns at the specified position in the grid. Any cells
	 * which have been added to the grid prior to adding the columns will be
	 * adjusted as follows: on each row, the cell which overlaps or whose right
	 * edge touches the insert position will be expanded to fill the added
	 * columns.
	 * 
	 * @param index
	 *            the insert position.
	 * @param columns
	 *            the columns to be inserted.
	 * @see GridColumn#parse(String)
	 */
	public void addColumns(int index, String columns) {
		addColumns(index, parseColumns(columns));
	}

	/**
	 * Inserts the columns at the specified position in the grid. Any cells
	 * which have been added to the grid prior to adding the columns will be
	 * adjusted as follows: on each row, the cell which overlaps or whose right
	 * edge touches the insert position will be expanded to fill the added
	 * columns.
	 * 
	 * @param index
	 *            the insert position.
	 * @param columns
	 *            the columns to be inserted.
	 * @see GridColumn#parse(String)
	 */
	public void addColumns(int index, GridColumn[] columns) {
		checkColumnInsert(index);
		Util.noNulls(columns);

		this.columns.addAll(index, Arrays.asList(columns));

		adjustForColumnInsert(index, columns.length);
	}

	private void checkColumnInsert(int index) {
		if (index < 0 || index > this.columns.size())
			PaperClips.error(SWT.ERROR_INVALID_RANGE,
					"index = " + index + ", size = " + this.columns.size()); //$NON-NLS-1$ //$NON-NLS-2$
	}

	private void adjustForColumnInsert(int index, int count) {
		adjustCellsForColumnInsert(header, index, count);
		adjustCellsForColumnInsert(body, index, count);
		adjustCellsForColumnInsert(footer, index, count);

		adjustColumnGroupsForColumnInsert(index, count);

		if (bodyCol > index)
			bodyCol += count;
		if (headerCol > index)
			headerCol += count;
		if (footerCol > index)
			footerCol += count;
	}

	private void adjustCellsForColumnInsert(List rows, int index, int count) {
		for (int rowI = 0; rowI < rows.size(); rowI++) {
			List row = (List) rows.get(rowI);
			int col = 0;
			for (int cellI = 0; cellI < row.size(); cellI++) {
				GridCell cell = (GridCell) row.get(cellI);
				col += cell.colspan;

				// Adjust the cell which extends through the insert point, or
				// whose right side touches the insert
				// point. Except on the last row, don't adjust the final cell if
				// it only touches the insert point
				// (the user may be adding columns right before s/he adds column
				// headers).
				if ( // cell overlaps insert point, or
				(col > index) ||
				// right side touches insert point but is not the final cell.
						(col == index && (rowI + 1 < rows.size() || cellI + 1 < row
								.size()))) {
					row
							.set(cellI, new GridCell(cell.hAlignment,
									cell.vAlignment, cell.target, cell.colspan
											+ count));
					break;
				}
			}
		}
	}

	private void adjustColumnGroupsForColumnInsert(int index, int count) {
		for (int groupI = 0; groupI < columnGroups.length; groupI++) {
			int[] group = columnGroups[groupI];
			for (int i = 0; i < group.length; i++)
				if (group[i] >= index)
					group[i] += count;
		}
	}

	/**
	 * Separates the comma-separated argument and parses each piece to obtain an
	 * array of GridColumns.
	 * 
	 * @param columns
	 *            the comma-separated list of column specs.
	 * @return GridColumn array with the requested columns.
	 */
	private static GridColumn[] parseColumns(String columns) {
		Util.notNull(columns);
		String[] cols = columns.split("\\s*,\\s*"); //$NON-NLS-1$

		GridColumn[] result = new GridColumn[cols.length];
		for (int i = 0; i < cols.length; i++)
			result[i] = GridColumn.parse(cols[i]);

		return result;
	}

	/**
	 * Returns an array of <code>GridColumn</code>s which are the columns in the
	 * receiver.
	 * 
	 * @return an array of <code>GridColumn</code>s which are the columns in the
	 *         receiver.
	 */
	public GridColumn[] getColumns() {
		return (GridColumn[]) columns.toArray(new GridColumn[columns.size()]);
	}

	/**
	 * Adds the Print to the grid header, with default alignment and a colspan
	 * of 1.
	 * 
	 * @param cell
	 *            the print to add.
	 */
	public void addHeader(Print cell) {
		headerCol = add(header, headerCol, SWT.DEFAULT, SWT.DEFAULT, cell, 1);
	}

	/**
	 * Adds the Print to the grid header, using the given alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param cell
	 *            the print to add.
	 */
	public void addHeader(int hAlignment, Print cell) {
		headerCol = add(header, headerCol, hAlignment, SWT.DEFAULT, cell, 1);
	}

	/**
	 * Adds the Print to the grid header, using the given alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param vAlignment
	 *            the vertical alignment of the print within the grid cell. One
	 *            of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
	 *            {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
	 *            indicates that the cell is vertically greedy, so GridPrint
	 *            will limit the cell's height to the tallest non-FILL cell in
	 *            the row.
	 * @param cell
	 *            the print to add.
	 */
	public void addHeader(int hAlignment, int vAlignment, Print cell) {
		headerCol = add(header, headerCol, hAlignment, vAlignment, cell, 1);
	}

	/**
	 * Adds the Print to the grid header, with the given colspan and the default
	 * alignment.
	 * 
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 */
	public void addHeader(Print cell, int colspan) {
		headerCol = add(header, headerCol, SWT.DEFAULT, SWT.DEFAULT, cell,
				colspan);
	}

	/**
	 * Adds the Print to the grid header, using the given colspan and alignment.
	 * 
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 */
	public void addHeader(int hAlignment, Print cell, int colspan) {
		headerCol = add(header, headerCol, hAlignment, SWT.DEFAULT, cell,
				colspan);
	}

	/**
	 * Adds the Print to the grid header, using the given colspan and alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param vAlignment
	 *            the vertical alignment of the print within the grid cell. One
	 *            of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
	 *            {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
	 *            indicates that the cell is vertically greedy, so GridPrint
	 *            will limit the cell's height to the tallest non-FILL cell in
	 *            the row.
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 */
	public void addHeader(int hAlignment, int vAlignment, Print cell,
			int colspan) {
		headerCol = add(header, headerCol, hAlignment, vAlignment, cell,
				colspan);
	}

	/**
	 * Adds the Print to the grid header, using the given colspan and alignment.
	 * 
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @deprecated Use {@link #addHeader(int, Print, int)} instead. GridPrint's
	 *             addHeader method signatures have been rearranged to coincide
	 *             with the GridColumn column spec format:
	 *             [alignment]:content:[colspan]
	 */
	public void addHeader(Print cell, int colspan, int hAlignment) {
		headerCol = add(header, headerCol, hAlignment, SWT.DEFAULT, cell,
				colspan);
	}

	/**
	 * Returns an array containing the header cells in this grid. Each inner
	 * array represents one row in the header.
	 * 
	 * @return an array containing the header cells in this grid.
	 */
	public GridCell[][] getHeaderCells() {
		return getGridCellArray(header);
	}

	/**
	 * Returns an array containing the body cells in the grid. Each inner array
	 * represents one row in the body.
	 * 
	 * @return an array containing the body cells in the grid.
	 */
	public GridCell[][] getBodyCells() {
		return getGridCellArray(body);
	}

	/**
	 * Returns an array containing the footer cells in the grid. Each inner
	 * array represents one row in the footer.
	 * 
	 * @return an array containing the footer cells in the grid.
	 */
	public GridCell[][] getFooterCells() {
		return getGridCellArray(footer);
	}

	private static GridCell[][] getGridCellArray(List list) {
		GridCell[][] cells = new GridCell[list.size()][];
		for (int rowIndex = 0; rowIndex < cells.length; rowIndex++) {
			List row = (List) list.get(rowIndex);
			GridCell[] rowCells = new GridCell[row.size()];
			for (int cellIndex = 0; cellIndex < rowCells.length; cellIndex++)
				rowCells[cellIndex] = (GridCell) row.get(cellIndex);
			cells[rowIndex] = rowCells;
		}
		return cells;
	}

	/**
	 * Returns the background color of the header cells (defaults to the body
	 * background if null).
	 * 
	 * @return the background color of the header cells (defaults to the body
	 *         background if null).
	 * @deprecated this functionality has been moved to DefaultGridLook.
	 */
	public RGB getHeaderBackground() {
		return defaultLook.getHeaderBackground();
	}

	/**
	 * Sets the background color of the header cells.
	 * 
	 * @param headerBackground
	 *            the new background color (defaults to the body background if
	 *            null).
	 * @deprecated this functionality has been moved to DefaultGridLook. Set a
	 *             DefaultGridLook on the grid, then call setHeaderBackground on
	 *             the grid look.
	 */
	public void setHeaderBackground(RGB headerBackground) {
		defaultLook.setHeaderBackground(headerBackground);
	}

	/**
	 * Adds the Print to the grid body, with the default alignment and a colspan
	 * of 1.
	 * 
	 * @param cell
	 *            the print to add.
	 */
	public void add(Print cell) {
		bodyCol = add(body, bodyCol, SWT.DEFAULT, SWT.DEFAULT, cell, 1);
	}

	/**
	 * Adds the Print to the grid body, using the given colspan and alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param cell
	 *            the print to add.
	 */
	public void add(int hAlignment, Print cell) {
		bodyCol = add(body, bodyCol, hAlignment, SWT.DEFAULT, cell, 1);
	}

	/**
	 * Adds the Print to the grid body, using the given colspan and alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param vAlignment
	 *            the vertical alignment of the print within the grid cell. One
	 *            of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
	 *            {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
	 *            indicates that the cell is vertically greedy, so GridPrint
	 *            will limit the cell's height to the tallest non-FILL cell in
	 *            the row.
	 * @param cell
	 *            the print to add.
	 */
	public void add(int hAlignment, int vAlignment, Print cell) {
		bodyCol = add(body, bodyCol, hAlignment, vAlignment, cell, 1);
	}

	/**
	 * Adds the Print to the grid body, with the given colspan and the default
	 * alignment.
	 * 
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 */
	public void add(Print cell, int colspan) {
		bodyCol = add(body, bodyCol, SWT.DEFAULT, SWT.DEFAULT, cell, colspan);
	}

	/**
	 * Adds the Print to the grid body, using the given colspan and alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 */
	public void add(int hAlignment, Print cell, int colspan) {
		bodyCol = add(body, bodyCol, hAlignment, SWT.DEFAULT, cell, colspan);
	}

	/**
	 * Adds the Print to the grid body, using the given colspan and alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param vAlignment
	 *            the vertical alignment of the print within the grid cell. One
	 *            of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
	 *            {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
	 *            indicates that the cell is vertically greedy, so GridPrint
	 *            will limit the cell's height to the tallest non-FILL cell in
	 *            the row.
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 */
	public void add(int hAlignment, int vAlignment, Print cell, int colspan) {
		bodyCol = add(body, bodyCol, hAlignment, vAlignment, cell, colspan);
	}

	/**
	 * Adds the Print to the grid body, using the given colspan and alignment.
	 * 
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @deprecated Use {@link #add(int, Print, int)} instead. GridPrint's add
	 *             method signatures have been rearranged to coincide with the
	 *             GridColumn column spec format: [alignment]:content:[colspan]
	 */
	public void add(Print cell, int colspan, int hAlignment) {
		bodyCol = add(body, bodyCol, hAlignment, SWT.DEFAULT, cell, colspan);
	}

	/**
	 * Returns the background color of the body cells (no background color if
	 * null).
	 * 
	 * @return the background color of the body cells (no background color if
	 *         null).
	 * @deprecated this functionality has been moved to DefaultGridLook.
	 */
	public RGB getBodyBackground() {
		return defaultLook.getBodyBackground();
	}

	/**
	 * Sets the background color of the body cells.
	 * 
	 * @param bodyBackground
	 *            the new background color (no background is drawn if null).
	 * @deprecated this functionality has been moved to DefaultGridLook. Set a
	 *             DefaultGridLook on the grid, then call setBodyBackground on
	 *             the grid look.
	 */
	public void setBodyBackground(RGB bodyBackground) {
		defaultLook.setBodyBackground(bodyBackground);
	}

	/**
	 * Returns whether individual body cells in the grid may be broken across
	 * pages. Defaults to true.
	 * 
	 * @return whether individual body cells in the grid may be broken across
	 *         pages.
	 */
	public boolean isCellClippingEnabled() {
		return cellClippingEnabled;
	}

	/**
	 * Sets whether individual body cells in the grid may be broken across
	 * pages.
	 * 
	 * @param cellClippingEnabled
	 *            whether to enabled cell clipping.
	 */
	public void setCellClippingEnabled(boolean cellClippingEnabled) {
		this.cellClippingEnabled = cellClippingEnabled;
	}

	/**
	 * Adds the Print to the grid footer, with the default alignment and a
	 * colspan of 1.
	 * 
	 * @param cell
	 *            the print to add.
	 */
	public void addFooter(Print cell) {
		footerCol = add(footer, footerCol, SWT.DEFAULT, SWT.DEFAULT, cell, 1);
	}

	/**
	 * Adds the Print to the grid footer, using the given colspan and alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param cell
	 *            the print to add.
	 */
	public void addFooter(int hAlignment, Print cell) {
		footerCol = add(footer, footerCol, hAlignment, SWT.DEFAULT, cell, 1);
	}

	/**
	 * Adds the Print to the grid footer, using the given colspan and alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param vAlignment
	 *            the vertical alignment of the print within the grid cell. One
	 *            of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
	 *            {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
	 *            indicates that the cell is vertically greedy, so GridPrint
	 *            will limit the cell's height to the tallest non-FILL cell in
	 *            the row.
	 * @param cell
	 *            the print to add.
	 */
	public void addFooter(int hAlignment, int vAlignment, Print cell) {
		footerCol = add(footer, footerCol, hAlignment, vAlignment, cell, 1);
	}

	/**
	 * Adds the Print to the grid footer, with the given colspan and the default
	 * alignment.
	 * 
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 */
	public void addFooter(Print cell, int colspan) {
		footerCol = add(footer, footerCol, SWT.DEFAULT, SWT.DEFAULT, cell,
				colspan);
	}

	/**
	 * Adds the Print to the grid footer, using the given colspan and alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 */
	public void addFooter(int hAlignment, Print cell, int colspan) {
		footerCol = add(footer, footerCol, hAlignment, SWT.DEFAULT, cell,
				colspan);
	}

	/**
	 * Adds the Print to the grid footer, using the given colspan and alignment.
	 * 
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @param vAlignment
	 *            the vertical alignment of the print within the grid cell. One
	 *            of {@link SWT#DEFAULT}, {@link SWT#TOP}, {@link SWT#CENTER},
	 *            {@link SWT#BOTTOM}, or {@link SWT#FILL}. A value of FILL
	 *            indicates that the cell is vertically greedy, so GridPrint
	 *            will limit the cell's height to the tallest non-FILL cell in
	 *            the row.
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 */
	public void addFooter(int hAlignment, int vAlignment, Print cell,
			int colspan) {
		footerCol = add(footer, footerCol, hAlignment, vAlignment, cell,
				colspan);
	}

	/**
	 * Adds the Print to the grid footer, using the given colspan and alignment.
	 * 
	 * @param cell
	 *            the print to add.
	 * @param colspan
	 *            the number of columns to span, or {@link GridPrint#REMAINDER}
	 *            to span the rest of the row.
	 * @param hAlignment
	 *            the horizontal alignment of the print within the grid cell.
	 *            One of {@link SWT#DEFAULT} , {@link SWT#LEFT},
	 *            {@link SWT#CENTER} or {@link SWT#RIGHT}.
	 * @deprecated Use {@link #addFooter(int, Print, int)} instead. GridPrint's
	 *             addFooter method signatures have been rearranged to coincide
	 *             with the GridColumn column spec format:
	 *             [alignment]:content:[colspan]
	 */
	public void addFooter(Print cell, int colspan, int hAlignment) {
		footerCol = add(footer, footerCol, hAlignment, SWT.DEFAULT, cell,
				colspan);
	}

	/**
	 * Returns the background color of the footer cells.
	 * 
	 * @return the background color of the footer cells (defaults to body
	 *         background if null).
	 * @deprecated this functionality has been moved to DefualtGridLook.
	 */
	public RGB getFooterBackground() {
		return defaultLook.getFooterBackground();
	}

	/**
	 * Sets the background color of the footer cells.
	 * 
	 * @param footerBackground
	 *            the new background color (defaults to body background if
	 *            null).
	 * @deprecated this functionality has been moved to DefaultGridLook. Set a
	 *             DefaultGridLook on the grid, then call setFooterBackground on
	 *             the grid look.
	 */
	public void setFooterBackground(RGB footerBackground) {
		defaultLook.setBodyBackground(footerBackground);
	}

	/*
	 * Returns the column number that we've advanced to, after adding the new
	 * cell.
	 */
	private int add(
			List rows, // List of List of GridCell
			int startColumn, int hAlignment, int vAlignment,
			Print cellContents, int colspan) {
		startColumn = startNewRowIfCurrentRowFull(startColumn);
		checkColumnSpan(startColumn, colspan);
		List row = getOpenRow(rows, startColumn);
		colspan = convertRemainderToExplicitColSpan(startColumn, colspan);

		GridCell cell = new GridCell(hAlignment, vAlignment, cellContents,
				colspan);
		row.add(cell);
		startColumn += colspan;

		// Make sure column number is valid.
		if (startColumn > columns.size()) {
			// THIS SHOULD NOT HAPPEN--ABOVE LOGIC SHOULD PREVENT THIS CASE
			// ..but just in case.

			row.remove(row.size() - 1);
			if (row.size() == 0)
				rows.remove(row);

			PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
					"Colspan " + colspan + " too wide at column " //$NON-NLS-1$ //$NON-NLS-2$
							+ startColumn
							+ " (" + columns.size() + " columns total)"); //$NON-NLS-1$//$NON-NLS-2$
		}

		return startColumn;
	}

	private int convertRemainderToExplicitColSpan(int startColumn, int colspan) {
		if (colspan == REMAINDER)
			colspan = columns.size() - startColumn;
		return colspan;
	}

	private int startNewRowIfCurrentRowFull(int startColumn) {
		// If we're at the end of a row, start a new row.
		if (startColumn == columns.size())
			startColumn = 0;
		return startColumn;
	}

	private void checkColumnSpan(int startColumn, int colspan) {
		if (startColumn + colspan > columns.size())
			PaperClips.error(SWT.ERROR_INVALID_ARGUMENT,
					"Colspan " + colspan + " too wide at column " //$NON-NLS-1$ //$NON-NLS-2$
							+ startColumn
							+ " (" + columns.size() + " columns total)"); //$NON-NLS-1$//$NON-NLS-2$
	}

	private List getOpenRow(List rows, int startColumn) {
		List row; // the row we will add the cell to.
		if (startColumn == 0)
			// Start a new row if back at column 0.
			rows.add(row = new ArrayList(columns.size()));
		else
			// Get the incomplete row.
			row = (List) rows.get(rows.size() - 1); // List of GridCell
		return row;
	}

	/**
	 * Returns current column groups. The returned array may be modified without
	 * affecting this GridPrint.
	 * 
	 * @return the column groups.
	 */
	public int[][] getColumnGroups() {
		return PaperClipsUtil.copy(columnGroups);
	}

	/**
	 * Sets the column groups to the given two-dimension array. Each int[] array
	 * is a group. Columns in a group will be the same size when laid out on the
	 * print device.
	 * <p>
	 * The following statement causes columns 0 and 2 to be the same size, and
	 * columns 1 and 3 to be the same size.
	 * 
	 * <pre>
	 * grid.setColumnGroups(new int[][] { { 0, 2 }, { 1, 3 } });
	 * </pre>
	 * 
	 * <p>
	 * The behavior of this property is undefined when a column belongs to more
	 * than one group.
	 * <p>
	 * <b>Note:</b> Column grouping is enforced <i>before</i> column weights.
	 * Therefore, columns in the same group should be given the same weight to
	 * ensure they are laid out at the same width.
	 * 
	 * @param columnGroups
	 *            the new column groups.
	 */
	public void setColumnGroups(int[][] columnGroups) {
		checkColumnGroups(columnGroups);
		this.columnGroups = PaperClipsUtil.copy(columnGroups);
	}

	private void checkColumnGroups(int[][] columnGroups) {
		Util.notNull(columnGroups);
		for (int groupIndex = 0; groupIndex < columnGroups.length; groupIndex++)
			checkColumnGroup(columnGroups[groupIndex]);
	}

	private void checkColumnGroup(int[] columnGroup) {
		Util.notNull(columnGroup);
		for (int columnInGroupIndex = 0; columnInGroupIndex < columnGroup.length; columnInGroupIndex++)
			checkColumnIndex(columnGroup[columnInGroupIndex]);
	}

	private void checkColumnIndex(int columnIndex) {
		if (columnIndex < 0 || columnIndex >= columns.size())
			PaperClips.error(SWT.ERROR_INVALID_RANGE,
					"Column index in column group must be " + "0 <= " //$NON-NLS-1$ //$NON-NLS-2$
							+ columnIndex + " < " + columns.size()); //$NON-NLS-1$
	}

	/**
	 * Returns the border used around each cell.
	 * 
	 * @return the border used around each cell.
	 * @deprecated this functionality has been moved to DefaultGridLook.
	 */
	public Border getCellBorder() {
		return defaultLook.getCellBorder();
	}

	/**
	 * Sets the border around each of the grid's cells to the argument.
	 * 
	 * @param border
	 *            the new body cell border.
	 * @deprecated this functionality has been moved to DefaultGridLook. Set a
	 *             DefaultGridLook on the grid, then call setCellBorder on the
	 *             grid look.
	 */
	public void setCellBorder(Border border) {
		defaultLook.setCellBorder(border);
	}

	/**
	 * Returns the horizontal spacing between grid cells.
	 * 
	 * @return the horizontal spacing between grid cells.
	 * @deprecated this functionality has been moved to DefaultGridLook.
	 */
	public int getHorizontalSpacing() {
		return defaultLook.getCellSpacing().x;
	}

	/**
	 * Sets the horizontal spacing between grid cells.
	 * 
	 * @param horizontalSpacing
	 *            the new horizontal spacing. A value of {@link #BORDER_OVERLAP}
	 *            indicates that the borders should be overlapped instead of
	 *            spaced.
	 * @deprecated this functionality has been moved to DefaultGridLook. Set a
	 *             DefaultGridLook on the grid, then call setCellSpacing(Point)
	 *             on the grid look.
	 */
	public void setHorizontalSpacing(int horizontalSpacing) {
		defaultLook.setCellSpacing(horizontalSpacing, defaultLook
				.getCellSpacing().y);
	}

	/**
	 * Returns the vertical spacing between grid cells.
	 * 
	 * @return the vertical spacing between grid cells.
	 * @deprecated this functionality has been moved to DefaultGridLook.
	 */
	public int getVerticalSpacing() {
		return defaultLook.getCellSpacing().y;
	}

	/**
	 * Sets the vertical spacing between grid cells.
	 * 
	 * @param verticalSpacing
	 *            the new vertical spacing. A value of {@link #BORDER_OVERLAP}
	 *            indicates that the borders should be overlapped instead of
	 *            spaced.
	 * @deprecated this functionality has been moved to DefaultGridLook. Set a
	 *             DefaultGridLook on the grid, then call setCellSpacing(Point)
	 *             on the grid look.
	 */
	public void setVerticalSpacing(int verticalSpacing) {
		defaultLook.setCellSpacing(defaultLook.getCellSpacing().x,
				verticalSpacing);
	}

	/**
	 * Returns the grid's look. A GridLook determines what decorations will
	 * appear around the grid's contents. Default is a DefaultGridLook with no
	 * cell spacing, no cell borders, and no background colors.
	 * 
	 * @return the look of this grid.
	 */
	public GridLook getLook() {
		return look;
	}

	/**
	 * Sets the grid's look.
	 * 
	 * @param look
	 *            the new look.
	 */
	public void setLook(GridLook look) {
		Util.notNull(look);
		this.look = look;
	}

	public PrintIterator iterator(Device device, GC gc) {
		return new GridIterator(this, device, gc);
	}
}
