components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/layout/AbstractTableLayout.java
changeset 827 0944d8c0158b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/layout/AbstractTableLayout.java	Thu May 24 04:16:47 2012 -0400
@@ -0,0 +1,630 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+ */
+
+package com.oracle.solaris.vp.util.swing.layout;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+import com.oracle.solaris.vp.util.swing.layout.AbstractLayout.SizedSet;
+import com.oracle.solaris.vp.util.swing.layout.Anchor.AnchorType;
+
+public abstract class AbstractTableLayout<C extends HasAnchors>
+    extends ConstrainedLayout<C> implements HasAnchors {
+
+    //
+    // Inner classes
+    //
+
+    protected static class CellInfo {
+	//
+	// Instance data
+	//
+
+	private Component component;
+	private Dimension size;
+	private boolean preferred;
+	private List<RowOrCol> parents = new ArrayList<RowOrCol>();
+
+	//
+	// Constructors
+	//
+
+	public CellInfo(Component component, boolean preferred) {
+	    this.component = component;
+	    this.preferred = preferred;
+	}
+
+	//
+	// CellInfo methods
+	//
+
+	public void add(RowOrCol parent) {
+	    parents.add(parent);
+	}
+
+	public Component getComponent() {
+	    return component;
+	}
+
+	public Dimension getSize() {
+	    // Deferred fetch
+	    if (size == null) {
+		size = preferred ?
+		    component.getPreferredSize() : component.getMinimumSize();
+	    }
+	    return size;
+	}
+
+	public void remove() {
+	    for (int i = parents.size() - 1; i >= 0; i--) {
+		parents.get(i).childRemoved(this);
+		parents.remove(i);
+	    }
+	}
+    }
+
+    protected static class RowOrCol {
+	//
+	// Instance data
+	//
+
+	private AbstractTableConstraint constraint;
+	private boolean isRow;
+	private int size = -1;
+	private float weight = -1f;
+	private List<CellInfo> children = new ArrayList<CellInfo>();
+	private List<CellInfo> roChildren =
+	    Collections.unmodifiableList(children);
+
+	//
+	// Constructors
+	//
+
+	public RowOrCol(AbstractTableConstraint constraint, boolean isRow) {
+	    this.constraint = constraint;
+	    this.isRow = isRow;
+	}
+
+	//
+	// Instance data
+	//
+
+	public void add(CellInfo child) {
+	    children.add(child);
+	    child.add(this);
+	}
+
+	/**
+	 * Called by the child when it is removed from layout.
+	 */
+	public boolean childRemoved(CellInfo child) {
+	    boolean ret = children.remove(child);
+	    if (ret) {
+		size = -1;
+	    }
+	    return ret;
+	}
+
+	public AbstractTableConstraint getConstraint() {
+	    return constraint;
+	}
+
+	public List<CellInfo> getChildren() {
+	    return roChildren;
+	}
+
+	/**
+	 * Get the maximum size (row height or column width) of every child
+	 * {@code Component}.
+	 */
+	public int getSize() {
+	    // Deferred fetch
+	    if (size == -1) {
+		for (CellInfo child : children) {
+		    Dimension d = child.getSize();
+		    int s = isRow ? d.height : d.width;
+		    if (s > size) {
+			size = s;
+		    }
+		}
+	    }
+	    return size;
+	}
+
+	public float getWeight() {
+	    // Deferred fetch
+	    if (weight == -1) {
+		weight = constraint.getWeight();
+	    }
+	    return weight;
+	}
+
+	/**
+	 * Remove all children in this row/column from layout (in any
+	 * row/column).
+	 */
+	public void remove() {
+	    for (int i = children.size() - 1; i >= 0; i--) {
+		remove(children.get(i));
+	    }
+	}
+
+	/**
+	 * Remove the given child in this row/column from layout (in any
+	 * row/column).
+	 */
+	public boolean remove(CellInfo child) {
+	    if (children.contains(child)) {
+		child.remove();
+		return true;
+	    }
+	    return false;
+	}
+
+	public void setSize(int size) {
+	    this.size = size;
+	}
+
+	public void setWeight(float weight) {
+	    this.weight = weight;
+	}
+    }
+
+    protected static class RowOrColSet implements SizedSet {
+	//
+	// Instance data
+	//
+
+	private int tSizes = -1;
+	private int tGaps = -1;
+	private List<RowOrCol> elements = new ArrayList<RowOrCol>();
+	private List<RowOrCol> roElements =
+	    Collections.unmodifiableList(elements);
+
+	//
+	// SizedSet methods
+	//
+
+	@Override
+	public int getCount() {
+	    return elements.size();
+	}
+
+	@Override
+	public int getSize(int i) {
+	    return elements.get(i).getSize();
+	}
+
+	@Override
+	public float getWeight(int i) {
+	    return elements.get(i).getWeight();
+	}
+
+	@Override
+	public void setSize(int i, int size) {
+	    elements.get(i).setSize(size);
+	}
+
+	@Override
+	public void setWeight(int i, float weight) {
+	    elements.get(i).setWeight(weight);
+	}
+
+	//
+	// RowOrColSet methods
+	//
+
+	public void add(RowOrCol element) {
+	    elements.add(element);
+	}
+
+	public void add(int i, RowOrCol element) {
+	    elements.add(i, element);
+	}
+
+	public List<RowOrCol> getElements() {
+	    return roElements;
+	}
+
+	public int getGap(int i) {
+	    AbstractTableConstraint constraint =
+		elements.get(i).getConstraint();
+
+	    return (i == 0 && constraint.getIgnoreFirstGap()) ?
+		0 : constraint.getGap();
+	}
+
+	public int getGapTotal() {
+	    // Deferred fetch
+	    if (tGaps == -1) {
+		tGaps = 0;
+		for (int i = 0, n = getCount(); i < n; i++) {
+		    tGaps += getGap(i);
+		}
+	    }
+	    return tGaps;
+	}
+
+	public int getSizeTotal() {
+	    // Deferred fetch
+	    if (tSizes == -1) {
+		tSizes = 0;
+		for (RowOrCol element : elements) {
+		    tSizes += element.getSize();
+		}
+	    }
+	    return tSizes;
+	}
+
+	public void remove(int i) {
+	    RowOrCol element = elements.remove(i);
+	    if (element != null) {
+		element.remove();
+		tSizes = -1;
+		tGaps = -1;
+	    }
+	}
+    }
+
+    protected static class TableInfo {
+	//
+	// Instance data
+	//
+
+	private RowOrColSet cols;
+	private RowOrColSet rows;
+
+	//
+	// Constructors
+	//
+
+	public TableInfo(RowOrColSet cols, RowOrColSet rows) {
+	    this.cols = cols;
+	    this.rows = rows;
+
+	    // Remove rows/columns from layout if a) directed by constraints b)
+	    // they contain only invisible Components
+	    for (RowOrColSet set : new RowOrColSet[] {rows, cols}) {
+		List<RowOrCol> elements = set.getElements();
+		OUTER: for (int i = elements.size() - 1; i >= 0; i--) {
+		    RowOrCol element = elements.get(i);
+		    if (!element.getConstraint().getLayoutIfInvisible()) {
+			for (CellInfo comp : element.getChildren()) {
+			    if (comp.getComponent().isVisible()) {
+				continue OUTER;
+			    }
+			}
+
+			// Remove this row/column from layout
+			set.remove(i);
+		    }
+		}
+	    }
+	}
+
+	//
+	// TableInfo methods
+	//
+
+	public RowOrColSet getColumns() {
+	    return cols;
+	}
+
+	public RowOrColSet getRows() {
+	    return rows;
+	}
+    }
+
+    //
+    // Instance data
+    //
+
+    public HorizontalAnchor hAnchor;
+    public VerticalAnchor vAnchor;
+
+    //
+    // Constructors
+    //
+
+    public AbstractTableLayout(HorizontalAnchor hAnchor,
+	VerticalAnchor vAnchor, C defaultElementConstraint) {
+
+	super(defaultElementConstraint);
+	this.hAnchor = hAnchor;
+	this.vAnchor = vAnchor;
+    }
+
+    //
+    // HasAnchors methods
+    //
+
+    @Override
+    public HorizontalAnchor getHorizontalAnchor() {
+	return hAnchor;
+    }
+
+    @Override
+    public VerticalAnchor getVerticalAnchor() {
+	return vAnchor;
+    }
+
+    //
+    // LayoutManager methods
+    //
+
+    /**
+     * Lay out Components in the given Container.
+     */
+    @Override
+    public void layoutContainer(Container container) {
+	TableInfo info = getTableInfo(container, true);
+
+	RowOrColSet rows = info.getRows();
+	int nRows = rows.getCount();
+	if (nRows == 0) {
+	    return;
+	}
+
+	RowOrColSet cols = info.getColumns();
+	int nCols = cols.getCount();
+
+	Insets insets = container.getInsets();
+	Dimension aSize = container.getSize();
+
+	// The amount of space we have to work with
+	int cWidth = aSize.width - insets.left - insets.right;
+	int cHeight = aSize.height - insets.top - insets.bottom;
+
+	// Where to start drawing the first Component
+	int top = insets.top;
+	int tableLeft = insets.left;
+
+	// First set heights of rows
+	top += fitToSize(cHeight, rows, getVerticalAnchor());
+
+	// Then set widths of columns
+	tableLeft += fitToSize(cWidth, cols, getHorizontalAnchor());
+
+	// Lay out components
+	for (int r = 0; r < nRows; r++) {
+
+	    RowOrCol row = rows.getElements().get(r);
+	    int rSize = row.getSize();
+	    top += rows.getGap(r);
+
+	    List<CellInfo> children = row.getChildren();
+	    int left = tableLeft;
+	    for (int c = 0; c < nCols; c++) {
+
+		RowOrCol col = cols.getElements().get(c);
+		int cSize = col.getSize();
+		left += cols.getGap(c);
+
+		if (c < children.size()) {
+		    CellInfo cInfo = children.get(c);
+		    Component comp = cInfo.getComponent();
+		    Dimension pSize = cInfo.getSize();
+
+		    HasAnchors hasAnchors = compToConst.get(comp);
+
+		    int[] yAndHeight = getOffsetAndSize(rSize,
+			pSize.height, hasAnchors.getVerticalAnchor());
+
+		    int[] xAndWidth = getOffsetAndSize(cSize,
+			pSize.width, hasAnchors.getHorizontalAnchor());
+
+		    comp.setBounds(left + xAndWidth[0], top + yAndHeight[0],
+			xAndWidth[1], yAndHeight[1]);
+		}
+
+		left += cSize;
+	    }
+
+	    top += rSize;
+	}
+    }
+
+    //
+    // AbstractLayout methods
+    //
+
+    @Override
+    protected Dimension getLayoutSize(Container container, boolean preferred) {
+	TableInfo info = getTableInfo(container, preferred);
+
+	RowOrColSet cols = info.getColumns();
+	RowOrColSet rows = info.getRows();
+
+	int width = cols.getSizeTotal() + cols.getGapTotal();
+	int height = rows.getSizeTotal() + rows.getGapTotal();
+
+	Insets insets = container.getInsets();
+	width += insets.left + insets.right;
+	height += insets.top + insets.bottom;
+
+	return new Dimension(width, height);
+    }
+
+    protected TableInfo getTableInfo(Container container, boolean preferred) {
+	Component[] comps = getLayoutComponents(container.getComponents());
+
+	int nCols = getColumnCount(container);
+	int nRows = 0;
+	if (nCols != 0) {
+	    nRows = comps.length / nCols;
+	    if (comps.length % nCols > 0) {
+		nRows++;
+	    }
+	}
+
+	// Build cells
+	CellInfo[] cInfo = new CellInfo[comps.length];
+	for (int i = 0; i < comps.length; i++) {
+	    cInfo[i] = new CellInfo(comps[i], preferred);
+	}
+
+	// Build columns
+	RowOrColSet cols = new RowOrColSet();
+	for (int c = 0; c < nCols; c++) {
+	    AbstractTableConstraint constraint =
+		getColumnConstraint(container, c);
+
+	    RowOrCol col = new RowOrCol(constraint, false);
+
+	    for (int r = 0; r < nRows; r++) {
+		int index = r * nCols + c;
+		if (index < cInfo.length) {
+		    col.add(cInfo[index]);
+		}
+	    }
+
+	    cols.add(col);
+	}
+
+	// Build rows
+	RowOrColSet rows = new RowOrColSet();
+	for (int r = 0; r < nRows; r++) {
+	    AbstractTableConstraint constraint =
+		getRowConstraint(container, r);
+
+	    RowOrCol row = new RowOrCol(constraint, true);
+
+	    for (int c = 0; c < nCols; c++) {
+		int index = r * nCols + c;
+		if (index < cInfo.length) {
+		    row.add(cInfo[index]);
+		}
+	    }
+
+	    rows.add(row);
+	}
+
+	return new TableInfo(cols, rows);
+    }
+
+    //
+    // AbstractTableLayout methods
+    //
+
+    protected abstract AbstractTableConstraint getColumnConstraint(
+	Container container, int col);
+
+    protected abstract AbstractTableConstraint getRowConstraint(
+	Container container, int row);
+
+    /**
+     * Gets the number of columns in this {@code AbstractTableLayout}.
+     *
+     * @param	    container
+     *		    the {@code Container} being layed out
+     */
+    protected abstract int getColumnCount(Container container);
+
+    public void setHorizontalAnchor(HorizontalAnchor hAnchor) {
+	this.hAnchor = hAnchor;
+    }
+
+    public void setVerticalAnchor(VerticalAnchor vAnchor) {
+	this.vAnchor = vAnchor;
+    }
+
+    //
+    // Private methods
+    //
+
+    private int fitToSize(int aSize, RowOrColSet set, Anchor anchor) {
+	int top = 0;
+	int tSizes = set.getSizeTotal();
+	int tGaps = set.getGapTotal();
+
+	// Excess/insufficient space
+	int extraSize = aSize - tSizes - tGaps;
+
+	if (extraSize != 0) {
+
+	    if (extraSize < 0 ||
+		anchor.getAnchorType() == Anchor.AnchorType.FILL) {
+
+		distributeSpace(extraSize, set);
+	    } else {
+
+		switch (anchor.getAnchorType()) {
+		    case RIGHT_BOTTOM:
+			top += (aSize - tSizes - tGaps);
+			break;
+
+		    case CENTER:
+			top += (aSize - tSizes - tGaps) / 2;
+			break;
+		}
+	    }
+	}
+
+	return top;
+    }
+
+    /**
+     * Returns a two-element array consisting of the offset and size of a
+     * resized {@code Component}.
+     *
+     * @param	    aSize
+     *		    the available size in which to fit the {@code Component}
+     *
+     * @param	    pSize
+     *		    the preferred size in which to fit the {@code Component}
+     *
+     * @param	    anchor
+     *		    the Anchor of the {@code Component}
+     */
+    private int[] getOffsetAndSize(int aSize, int pSize, Anchor anchor) {
+	// Excess/insufficient space
+	int extra = aSize - pSize;
+
+	int offset = 0;
+	int size = pSize;
+	Anchor.AnchorType type = anchor.getAnchorType();
+
+	if (extra != 0) {
+
+	    if (extra < 0 || type == Anchor.AnchorType.FILL) {
+		size = aSize;
+	    } else {
+
+		switch (type) {
+		    case RIGHT_BOTTOM:
+			offset = (aSize - pSize);
+			break;
+
+		    case CENTER:
+			offset = (aSize - pSize) / 2;
+			break;
+		}
+	    }
+	}
+
+	return new int[] {offset, size};
+    }
+}