/**
*
*/
package photoSpreadTable;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.util.ArrayList;
import java.util.Iterator;
import javax.management.RuntimeErrorException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import photoSpreadUtilities.ComputableDimension;
import photoSpreadUtilities.Const;
import photoSpreadUtilities.Const.Alignment;
/**
* @author paepcke
*
*/
public class PredictableEquiSizedGridLayout implements LayoutManager2 {
protected int _topMargin;
protected int _bottomMargin;
protected int _leftMargin;
protected int _rightMargin;
protected int _vGap;
protected int _hGap;
protected int _numCols;
protected int _numRows;
protected boolean _isValid = false;
protected boolean _lockLayoutAction = false;
// Const.ALIGNMENT.LEFT_ALIGNED, Const.ALIGNMENT.CENTER_ALIGNED,
// Const.ALIGNMENT.RIGHT_ALIGNED:
protected Alignment _hAlignment = Alignment.CENTER_H_ALIGNED;
// Alignment.TOP_V_ALIGNED, Const.Alignment.CENTER_V_ALIGNED,
// Const.Alignment.BOTTOM_V_ALIGNED,
protected Alignment _vAlignment = Alignment.CENTER_V_ALIGNED;
protected ArrayList<Integer> _maxWidthInCol = new ArrayList<Integer>();
protected ArrayList<Integer> _maxHeightInRow = new ArrayList<Integer>();
protected final static int _defaultTopMargin = 5;
protected final static int _defaultBottomMargin = 5;
protected final static int _defaultLeftMargin = 5;
protected final static int _defaultRightMargin = 5;
protected final static int _defaultVGap = 3;
protected final static int _defaultHGap = 3;
protected final static int _defaultNumRows = 1;
protected final static int _defaultNumCols = 1;
protected static enum SizeType {
PREFERRED_SIZE, MAX_SIZE, MIN_SIZE
};
/****************************************************
* Constructor(s)
*****************************************************/
public PredictableEquiSizedGridLayout(int topMargin, int bottomMargin,
int leftMargin, int rightMargin, int vGap, int hGap, int numRows,
int numCols) {
_topMargin = topMargin;
_bottomMargin = bottomMargin;
_leftMargin = leftMargin;
_rightMargin = rightMargin;
_vGap = vGap;
_hGap = hGap;
_numRows = numRows;
_numCols = numCols;
}
public PredictableEquiSizedGridLayout() {
this(_defaultTopMargin, _defaultBottomMargin, _defaultLeftMargin,
_defaultRightMargin, _defaultVGap, _defaultHGap,
_defaultNumRows, _defaultNumCols);
}
public PredictableEquiSizedGridLayout(int numCols, int hGap, int vGap) {
this(_defaultTopMargin, _defaultBottomMargin, _defaultLeftMargin,
_defaultRightMargin, vGap, hGap, _defaultNumRows, numCols);
}
public PredictableEquiSizedGridLayout(int numCols) {
this(_defaultTopMargin, _defaultBottomMargin, _defaultLeftMargin,
_defaultRightMargin, _defaultVGap, _defaultHGap,
_defaultNumRows, numCols);
}
/****************************************************
* Getters/Setters
*****************************************************/
public void setColumns(int numCols) {
_numCols = numCols;
// _numRows = Const.INVALID;
}
public void setColumnsRevalidate(int numCols, Container surroundingContainer) {
setColumns(numCols);
layoutContainer(surroundingContainer);
}
public void setHGap(int hGap) {
_hGap = hGap;
// _numRows = Const.INVALID;
}
public void setHGapRevalidate(int hGap, Container surroundingContainer) {
setHGap(hGap);
layoutContainer(surroundingContainer);
}
public void setVGap(int vGap) {
_vGap = vGap;
// _numRows = Const.INVALID;
}
public void setVGapRevalidate(int vGap, Container surroundingContainer) {
setVGap(vGap);
layoutContainer(surroundingContainer);
}
public int getRows() {
return Math.max(1, _numRows);
}
public int getColumns() {
return Math.max(1, _numCols);
}
public int getHGap() {
return _hGap;
}
public int getVGap() {
return _vGap;
}
/****************************************************
* Private (Inner) Classes
*****************************************************/
@SuppressWarnings("unchecked")
private class ContainerIterator<T> implements Iterator {
int _pos = 0;
int _numOfComponents;
Container _container;
public ContainerIterator(Container container) {
_container = container;
_numOfComponents = container.getComponentCount();
}
public boolean hasNext() {
return (_pos < _numOfComponents);
}
public T next() {
return (T) _container.getComponent(_pos++);
}
public int size() {
return _container.getComponentCount();
}
public void remove() {
throw new UnsupportedOperationException(
"The ContainerIterator does not support the remove() method.");
}
}
/****************************************************
* Methods
*****************************************************/
public boolean isValid() {
return _isValid;
}
/**
* Recomputes layout without redrawing anything. Every component in the
* surrounding container is told where it should be, so that the next
* container redraw or window pack() will show its components laid out
* according to all the current settings in this layout manager.
*
* @param surroundingContainer
* Container to which this layout manager instance provides
* service.
*/
public void validate(Container surroundingContainer) {
validateNumRows(surroundingContainer);
layoutContainer(surroundingContainer);
_isValid = true;
}
/**
* Computes the number of rows that result from the number of components in
* the surrounding container, and this layout manager's number of columns
* (i.e. value of _numCols)
*
* @see Also: validateNumRows(int)
* @param surroundingContainer
* Container to which this layout manager instance provides
* service.
*/
public void validateNumRows(Container surroundingContainer) {
validateNumRows(surroundingContainer.getComponentCount());
}
/**
* Computes the number of rows that result from the number of components in
* the surrounding container, and this layout manager's number of columns
* (i.e. value of _numCols)
*
* @see Also: validateNumRows(int)
* @param surroundingContainer
* Container to which this layout manager instance provides
* service.
*/
public void validateNumRows(int componentsToLayout) {
if (_numCols == Const.INVALID)
_numCols = _defaultNumCols;
_numRows = (int) Math.ceil(componentsToLayout / _numCols);
}
/**
* Do main work of laying out the surrounding container.
*
* @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
*/
@Override
public void layoutContainer(Container surroundingContainer) {
if (_lockLayoutAction)
return;
try {
_lockLayoutAction = true;
// Get the distance of the layour from the surrounding
// container. These are usually determined by the
// prevailing Look-and-Feel.
Insets insets = surroundingContainer.getInsets();
// x-Pos top left corner of first component:
int x0 = insets.left + _leftMargin;
int y0 = insets.top + _topMargin;
int currX = x0;
int currY = y0;
int xOffsetWithinCell = 0;
int yOffsetWithinCell = 0;
Component comp;
ComputableDimension prefSize = new ComputableDimension(0, 0);
// Initialize _maxHeightInRow and _maxWidthInCol, as well
// as each component's preferred size if it's not set:
laidOutSize(surroundingContainer, SizeType.PREFERRED_SIZE);
// Loop through all components that are currently in the
// container and give them a position (relative to
// the surrounding container that we're laying out),
// and a size:
ContainerIterator<Component> components = new ContainerIterator<Component>(
surroundingContainer);
for (int currRow = 0; currRow < _numRows; currRow++) {
if (!components.hasNext())
break;
for (int currCol = 0; currCol < _numCols; currCol++) {
if (!components.hasNext())
break;
comp = components.next();
if (comp.isPreferredSizeSet())
prefSize = new ComputableDimension(comp
.getPreferredSize());
else
prefSize = new ComputableDimension(comp.getSize());
// Compute spaces for horizontal and vertical
// alignment of the present component within
// its cell (currRow, currCol). This space depends
// on the AlignmentX and AlignmentY values:
switch (_hAlignment) {
case LEFT_H_ALIGNED:
xOffsetWithinCell = 0;
break;
case CENTER_H_ALIGNED:
xOffsetWithinCell = ((int) ((0.5 * _maxWidthInCol
.get(currCol)) - (0.5 * prefSize.width)));
break;
case RIGHT_H_ALIGNED:
xOffsetWithinCell = ((int) (_maxWidthInCol.get(currCol) - prefSize.width));
break;
}
switch (_vAlignment) {
case TOP_V_ALIGNED:
yOffsetWithinCell = 0;
break;
case CENTER_V_ALIGNED:
yOffsetWithinCell = ((int) ((0.5 * _maxHeightInRow
.get(currRow)) - (0.5 * prefSize.height)));
break;
case BOTTOM_V_ALIGNED:
yOffsetWithinCell = ((int) (_maxHeightInRow
.get(currRow) - prefSize.height));
break;
}
comp.setBounds(currX + xOffsetWithinCell, currY
+ yOffsetWithinCell, prefSize.width,
prefSize.height);
currX += _maxWidthInCol.get(currCol) + _hGap;
}
currX = x0;
currY += _maxHeightInRow.get(currRow) + _vGap;
}
} finally {
_lockLayoutAction = false;
}
}
/**
* Compute total width/height of the current layout for the given
* container's components. Rows and columns may have differering widths and
* heights. Each column is as wide as the widest component in that column.
* Each row is as high as the highest component in that row.
*
* <b>Also:</b> this method leaves the _maxHeightInRow ArrayList initialized
* to the height of each row: [HighestComponentRow0, HighestComponentRow1,
* ...]
*
* It leaves the _maxWidthInCol ArrayList initialized to the widest
* component size in the columns: [WidestComponentCol0, WidestComponentCol1,
* ...]
*
* @param surroundingContainer
* Container for which we are to compute the total width and
* height of the layout.
* @param sizeType
* : PREFERRED_SIZE, MIN_SIZE, or MAX_SIZE. Controls which size
* assumption to use for the components.
* @return Width and height of the total layout.
*/
protected ComputableDimension laidOutSize(Container surroundingContainer,
SizeType sizeType) {
Insets insets = surroundingContainer.getInsets();
ComputableDimension compDim = new ComputableDimension();
Component comp;
// Make sure that the _numRows variable is up to date:
// validateNumRows(surroundingContainer);
// Result: initialize to the Look/Feel distance
// on all four sides. We'll add component dimensions
// and inter-component distances one component at
// a time:
ComputableDimension totalLayoutSize = new ComputableDimension(
insets.left + insets.right + _leftMargin + _rightMargin,
insets.top + insets.bottom + _topMargin + _bottomMargin);
ContainerIterator<Component> components = new ContainerIterator<Component>(
surroundingContainer);
_maxWidthInCol.clear();
_maxHeightInRow.clear();
// Initialize arrays to zero:
for (int col = 0; col < _numCols; col++)
_maxWidthInCol.add(0);
validateNumRows(surroundingContainer);
for (int row = 0; row < _numRows; row++)
_maxHeightInRow.add(0);
for (int currRow = 0; currRow < _numRows; currRow++) {
if (!components.hasNext())
break;
for (int currCol = 0; currCol < _numCols; currCol++) {
if (!components.hasNext())
continue;
comp = components.next();
switch (sizeType) {
case PREFERRED_SIZE:
compDim = new ComputableDimension(comp.getPreferredSize());
break;
case MIN_SIZE:
compDim = new ComputableDimension(comp.getMinimumSize());
break;
case MAX_SIZE:
compDim = new ComputableDimension(comp.getMaximumSize());
break;
}
if (compDim.height > _maxHeightInRow.get(currRow))
_maxHeightInRow.set(currRow, compDim.height);
if (compDim.width > _maxWidthInCol.get(currCol))
_maxWidthInCol.set(currCol, compDim.width);
}
}
for (int colWidth : _maxWidthInCol) {
totalLayoutSize.width += colWidth + _hGap;
}
for (int rowHeight : _maxHeightInRow) {
totalLayoutSize.height += rowHeight + _vGap;
}
// During the last run through the nested loop we
// added _hGap to total width, and _vGap to total
// height in anticipation of another row/column.
// Since neither of those came, subtract them again:
totalLayoutSize.width -= _hGap;
totalLayoutSize.height -= _vGap;
return totalLayoutSize;
}
/**
* Cause horizontal alignment of components with their cells to be left,
* center, or right. Automatically cause an internal update of the layout so
* that next time the surrounding container is redrawn, the new alignment
* will be in effect.
*
* @param al
* Alignment: Const.Alignment.{LEFT_H_ALIGNED | CENTER_H_ALIGNED
* | RIGHT_H_ALIGNED}.
* @param surroundingContainer
* The container whose layout this LayoutManager is managing.
*/
public void setLayoutAlignmentXRevalidate(Alignment al,
Container surroundingContainer) {
setLayoutAlignmentX(al);
validate(surroundingContainer);
}
/**
* Cause horizontal alignment of components with their cells to be left,
* center, or right.
*
* @param al
* Alignment: Const.Alignment.{LEFT_H_ALIGNED | CENTER_H_ALIGNED
* | RIGHT_H_ALIGNED} See also the
* {@link #setLayoutAlignmentX(Float) setLayoutAlignmentX(Float)}
* method.
*/
public void setLayoutAlignmentX(Alignment al) {
_hAlignment = al;
}
/**
* Cause horizontal alignment of components with their cells to be left,
* center, or right.
*
* @param al
* Alignment code: 0, 0.5, 1 for left, centered, right,
* respectively.
*/
public void setLayoutAlignmentX(Float al) {
int integerizedAlignmentFloat = (int) Math.floor(al * 10);
switch (integerizedAlignmentFloat) {
case 0:
setLayoutAlignmentX(Alignment.LEFT_H_ALIGNED);
break;
case 5:
setLayoutAlignmentX(Alignment.CENTER_H_ALIGNED);
break;
case 10:
setLayoutAlignmentX(Alignment.RIGHT_H_ALIGNED);
break;
default:
throw new RuntimeErrorException(null,
"Only values 0, 0.5, and 1 are allowed as horizontal alignment values.");
}
}
/**
* Cause vertical alignment of components with their cells to be top,
* center, or bottom. Automatically cause an internal update of the layout
* so that next time the surrounding container is redrawn, the new alignment
* will be in effect.
*
* @param al
* Alignment: Const.Alignment.{TOP_V_ALIGNED | CENTER_V_ALIGNED |
* BOTTOM_V_ALIGNED}.
* @param surroundingContainer
* The container whose layout this LayoutManager is managing.
*/
public void setLayoutAlignmentYRevalidate(Alignment al,
Container surroundingContainer) {
setLayoutAlignmentY(al);
validate(surroundingContainer);
}
/**
* Cause vertical alignment of components with their cells to be top,
* center, or bottom.
*
* @param al
* Alignment: Const.Alignment.{TOP_V_ALIGNED | CENTER_V_ALIGNED |
* BOTTOM_V_ALIGNED} See also the
* {@link #setLayoutAlignmentY(Float) setLayoutAlignmentY(Float)}
* method.
*/
public void setLayoutAlignmentY(Alignment al) {
_vAlignment = al;
}
/**
* Cause vertical alignment of components with their cells to be top,
* center, or bottom.
*
* @param al
* Alignment code: 0, 0.5, 1 for top, centered, bottom,
* respectively.
*/
public void setLayoutAlignmentY(Float al) {
int integerizedAlignmentFloat = (int) Math.floor(al * 10);
switch (integerizedAlignmentFloat) {
case 0:
setLayoutAlignmentY(Alignment.TOP_V_ALIGNED);
break;
case 5:
setLayoutAlignmentY(Alignment.CENTER_V_ALIGNED);
break;
case 10:
setLayoutAlignmentY(Alignment.BOTTOM_V_ALIGNED);
break;
default:
throw new RuntimeErrorException(null,
"Only values 0, 0.5, and 1 are allowed as vertical alignment values.");
}
}
/**
* Returns 0, 0.5, or 1.0 depending on whether the horizontal alignment of
* components is left, center, or right aligned within each layout cell.
*
* @see java.awt.LayoutManager2#getLayoutAlignmentX(java.awt.Container)
*/
@Override
public float getLayoutAlignmentX(Container arg0) {
// Horizontal alignment:
switch (_hAlignment) {
case LEFT_H_ALIGNED:
return 0.0f;
case CENTER_H_ALIGNED:
return 0.5f;
case RIGHT_H_ALIGNED:
return 1.0f;
default:
return 0.5f;
}
}
/**
* Returns LEFT_H_ALIGNED, CENTER_H_ALIGNED, or RIGHT_H_ALIGNED depending on
* whether the horizontal alignment of components is left, center, or right
* aligned within each layout cell.
*/
public Alignment getLayoutAlignmentX() {
return _hAlignment;
}
/**
* Returns 0, 0.5, or 1.0 depending on whether the horizontal alignment of
* components is left, center, or right aligned within each layout cell.
*
* @see java.awt.LayoutManager2#getLayoutAlignmentY(java.awt.Container)
*/
@Override
public float getLayoutAlignmentY(Container arg0) {
// Vertical alignment:
switch (_vAlignment) {
case TOP_V_ALIGNED:
return 0.0f;
case CENTER_V_ALIGNED:
return 0.5f;
case BOTTOM_V_ALIGNED:
return 1.0f;
default:
return 0.5f;
}
}
/**
* Returns TOP_V_ALIGNED, CENTER_V_ALIGNED, or BOTTOM_V_ALIGNED depending on
* whether the vertical alignment of components is top, center, or bottom
* aligned within each layout cell.
*/
public Alignment getLayoutAlignmentY() {
return _vAlignment;
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager2#maximumLayoutSize(java.awt.Container)
*/
@Override
public Dimension maximumLayoutSize(Container target) {
return laidOutSize(target, SizeType.MAX_SIZE);
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
*/
@Override
public Dimension minimumLayoutSize(Container target) {
return laidOutSize(target, SizeType.MIN_SIZE);
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
*/
@Override
public Dimension preferredLayoutSize(Container target) {
return laidOutSize(target, SizeType.PREFERRED_SIZE);
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager2#invalidateLayout(java.awt.Container)
*/
@Override
public void invalidateLayout(Container target) {
_isValid = false;
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
*/
@Override
public void removeLayoutComponent(Component arg0) {
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager2#addLayoutComponent(java.awt.Component,
* java.lang.Object)
*/
@Override
public void addLayoutComponent(Component arg0, Object arg1) {
}
/*
* (non-Javadoc)
*
* @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
* java.awt.Component)
*/
@Override
public void addLayoutComponent(String arg0, Component arg1) {
}
/**
* Compute number of rows that fit into a given container under the special
* assumption that all components are of equal height. All other
* computations in this class look at the actual components that are stored
* in a surrounding container. This method does not. This method changes
* nothing in the layout. It simply performs the calculation and returns the
* result.
*
* @param surroundingContainer
* Container that contains this layout
* @param compHeight
* Height in pixels of the the equi-height components.
* @return Number of rows with specified equi-height components that would
* fit the specified container.
*/
public int getNumRowsAllSameHeightComponents(
Container surroundingContainer, int compHeight) {
int containerHeight = surroundingContainer.getHeight();
int trueCompHeight = compHeight + _vGap;
int heightOverhead = 0;
int numRows = 0;
// Prepare to subtract top/bottom white space from
// container height:
heightOverhead += surroundingContainer.getInsets().top;
heightOverhead += surroundingContainer.getInsets().bottom;
heightOverhead += _topMargin;
heightOverhead += _bottomMargin;
// Num of rows is container height after vertical white space
// has been subtracted, divided by the height of the equi-height
// components that the parameter gives us (trueCompHeight includes
// the vertical gaps between rows. The addition of _vGap in the
// numerator accounts for the absent vGap after the last row.
numRows = (int) Math.floor(((float) containerHeight
- (float) heightOverhead + (float) _vGap)
/ ((float) trueCompHeight));
return numRows;
}
/****************************************************
* Main and/or Testing Methods
*****************************************************/
public static void main(String[] argv) {
final int BUTTON_TEXT_INSET = 2;
JFrame window = new JFrame();
// Buttons for a 2*2 test layout:
JButton butt1 = new JButton("Button1");
butt1.setPreferredSize(BasicGraphicsUtils.getPreferredButtonSize(butt1,
BUTTON_TEXT_INSET));
JButton butt2 = new JButton("Button2 Longer");
butt2.setPreferredSize(BasicGraphicsUtils.getPreferredButtonSize(butt2,
BUTTON_TEXT_INSET));
JButton butt3 = new JButton("B3");
butt3.setPreferredSize(BasicGraphicsUtils.getPreferredButtonSize(butt3,
BUTTON_TEXT_INSET));
JButton butt4 = new JButton("Butt4");
butt4.setPreferredSize(BasicGraphicsUtils.getPreferredButtonSize(butt4,
BUTTON_TEXT_INSET));
PredictableEquiSizedGridLayout layoutDefault = new PredictableEquiSizedGridLayout();
// PredictableEquiSizedGridLayout layout2by2 = new
// PredictableEquiSizedGridLayout(2,2);
// Layout should be one column as wide as "Button2 Longer":
JPanel panel = new JPanel(layoutDefault);
panel.add(butt1);
panel.add(butt2);
panel.add(butt3);
panel.add(butt4);
panel.validate();
window.add(panel);
panel.setVisible(true);
window.pack();
window.setVisible(true);
layoutDefault.setLayoutAlignmentXRevalidate(Alignment.CENTER_H_ALIGNED,
panel);
layoutDefault.setLayoutAlignmentXRevalidate(Alignment.LEFT_H_ALIGNED,
panel);
layoutDefault.setLayoutAlignmentXRevalidate(Alignment.RIGHT_H_ALIGNED,
panel);
layoutDefault.setColumnsRevalidate(2, panel);
window.pack();
layoutDefault.setLayoutAlignmentXRevalidate(Alignment.CENTER_H_ALIGNED,
panel);
layoutDefault.setLayoutAlignmentXRevalidate(Alignment.LEFT_H_ALIGNED,
panel);
}
}