/*
Wotonomy: OpenStep design patterns for pure Java applications.
Copyright (C) 2000 Blacksmith, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see http://www.gnu.org
*/
package net.wotonomy.ui.swing.components;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
/**
* BetterFlowLayout works just like FlowLayout, except that you can specify a
* vertical orientation in addition to the usual horizontal orientations. You
* can also specify that all the components be sized to the same height and/or
* width. By default, the behavior is identical to FlowLayout.
*
* @author michael@mpowers.net
* @author $Author: cgruber $
* @version $Revision: 904 $ $Date: 2006-02-18 18:19:05 -0500 (Sat, 18 Feb 2006)
* $
*/
public class BetterFlowLayout extends FlowLayout {
/**
* This value indicates vertical orientation and that each column of components
* should be top-justified.
*/
public static final int TOP = 32;
/**
* This value indicates vertical orientation and that each column of components
* should be centered.
*/
public static final int CENTER_VERTICAL = 16;
/**
* This value indicates vertical orientation and that each column of components
* should be bottom-justified.
*/
public static final int BOTTOM = 8;
/**
* Tracks orientation.
*/
protected boolean isHorizontal = true;
/**
* Tracks component sizing of width.
*/
protected boolean isWidthUniform = false;
/**
* Tracks component sizing of height.
*/
protected boolean isHeightUniform = false;
/**
* Constructs a new Flow Layout with a centered alignment and a default 5-unit
* horizontal and vertical gap.
*/
public BetterFlowLayout() {
this(CENTER, 5, 5);
}
/**
* Constructs a new Flow Layout with the specified alignment and a default
* 5-unit horizontal and vertical gap. The value of the alignment argument must
* be one of BetterFlowLayout.LEFT,
* BetterFlowLayout.RIGHT, or BetterFlowLayout.CENTER.
*
* @param align the alignment value
*/
public BetterFlowLayout(int align) {
this(align, 5, 5);
}
/**
* Creates a new flow layout manager with the indicated alignment and the
* indicated horizontal and vertical gaps.
*
* The value of the alignment argument must be one of
* BetterFlowLayout.LEFT, BetterFlowLayout.RIGHT, or
* BetterFlowLayout.CENTER.
*
* @param align the alignment value.
* @param hgap the horizontal gap between components.
* @param vgap the vertical gap between components.
*/
public BetterFlowLayout(int align, int hgap, int vgap) {
setHgap(hgap);
setVgap(vgap);
setAlignment(align);
}
/**
* Sets whether all components should have the same height.
*
* @param isUniform the new value.
* @see #isHeightUniform
*/
public void setHeightUniform(boolean isUniform) {
isHeightUniform = isUniform;
}
/**
* Sets whether all components should have the same width.
*
* @param isUniform the new value.
* @see #isWidthUniform
*/
public void setWidthUniform(boolean isUniform) {
isWidthUniform = isUniform;
}
/**
* Determines whether all components will have the same height. The uniform
* height will be the maximum of the preferred heights of all the components in
* the container. This value defaults to false.
*
* @return whether components will have the same height.
*/
public boolean isHeightUniform() {
return isHeightUniform;
}
/**
* Determines whether all components will have the same width. The uniform
* height will be the maximum of the preferred widths of all the components in
* the container. This value defaults to false.
*
* @return whether components will have the same width.
*/
public boolean isWidthUniform() {
return isWidthUniform;
}
/**
* Sets the alignment for this layout. Possible values for horizontal
* orientation are LEFT, RIGHT, and
* CENTER. Possible values for vertical orientation are
* TOP, BOTTOM, and CENTER_VERTICAL.
*
* @param align the alignment value.
* @see java.awt.FlowLayout#getAlignment
*/
public void setAlignment(int align) {
if ((align == TOP) || (align == BOTTOM) || (align == CENTER_VERTICAL)) {
isHorizontal = false;
} else {
isHorizontal = true;
}
super.setAlignment(align);
}
/**
* Returns the preferred dimensions for this layout given the components in the
* specified target container.
*
* @param target the component which needs to be laid out
* @return the preferred dimensions to lay out the subcomponents of the
* specified container.
* @see Container
* @see #minimumLayoutSize
* @see java.awt.Container#getPreferredSize
*/
public Dimension preferredLayoutSize(Container target) {
if (isHorizontal) {
return preferredLayoutSizeHorizontal(target);
} else {
return preferredLayoutSizeVertical(target);
}
}
/**
* Returns the preferred dimensions for this layout given the components in the
* specified target container.
*
* @param target the component which needs to be laid out
* @return the preferred dimensions to lay out the subcomponents of the
* specified container.
* @see Container
* @see #minimumLayoutSize
* @see java.awt.Container#getPreferredSize
*/
public Dimension preferredLayoutSizeHorizontal(Container target) {
synchronized (target.getTreeLock()) {
Dimension dim = new Dimension(0, 0);
int nmembers = target.getComponentCount();
int maxWidth = 0;
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = m.getPreferredSize();
dim.height = Math.max(dim.height, d.height);
maxWidth = Math.max(maxWidth, d.width);
if (i > 0) {
dim.width += getHgap();
}
dim.width += d.width;
}
}
if (isWidthUniform)
dim.width = (maxWidth + getHgap()) * nmembers - getHgap();
Insets insets = target.getInsets();
dim.width += insets.left + insets.right + getHgap() * 2;
dim.height += insets.top + insets.bottom + getVgap() * 2;
return dim;
}
}
/**
* Returns the preferred dimensions for this layout given the components in the
* specified target container.
*
* @param target the component which needs to be laid out
* @return the preferred dimensions to lay out the subcomponents of the
* specified container.
* @see Container
* @see #minimumLayoutSize
* @see java.awt.Container#getPreferredSize
*/
public Dimension preferredLayoutSizeVertical(Container target) {
synchronized (target.getTreeLock()) {
Dimension dim = new Dimension(0, 0);
int nmembers = target.getComponentCount();
int maxHeight = 0;
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = m.getPreferredSize();
dim.width = Math.max(dim.width, d.width);
maxHeight = Math.max(maxHeight, d.height);
if (i > 0) {
dim.height += getVgap();
}
dim.height += d.height;
}
}
if (isHeightUniform)
dim.height = (maxHeight + getVgap()) * nmembers - getVgap();
Insets insets = target.getInsets();
dim.width += insets.left + insets.right + getHgap() * 2;
dim.height += insets.top + insets.bottom + getVgap() * 2;
return dim;
}
}
/**
* Returns the minimum dimensions needed to layout the components contained in
* the specified target container.
*
* @param target the component which needs to be laid out
* @return the minimum dimensions to lay out the subcomponents of the specified
* container.
* @see #preferredLayoutSize
* @see java.awt.Container
* @see java.awt.Container#doLayout
*/
public Dimension minimumLayoutSize(Container target) {
// preferred size is also the minimum size
if (isHorizontal) {
return preferredLayoutSizeHorizontal(target);
} else {
return preferredLayoutSizeVertical(target);
}
}
/**
* Lays out the container. This method lets each component take its preferred
* size by reshaping the components in the target container in order to satisfy
* the constraints of this BetterFlowLayout object.
*
* @param target the specified component being laid out.
* @see Container
* @see java.awt.Container#doLayout
*/
public void layoutContainer(Container target) {
if (isHorizontal) {
layoutContainerHorizontal(target);
} else {
layoutContainerVertical(target);
}
}
/**
* Lays out the container. This method lets each component take its preferred
* size by reshaping the components in the target container in order to satisfy
* the constraints of this BetterFlowLayout object.
*
* @param target the specified component being laid out.
* @see Container
* @see java.awt.Container#doLayout
*/
protected void layoutContainerHorizontal(Container target) {
synchronized (target.getTreeLock()) {
Insets insets = target.getInsets();
int maxwidth = target.getSize().width - (insets.left + insets.right + getHgap() * 2);
int nmembers = target.getComponentCount();
int x = 0, y = insets.top + getVgap();
int rowh = 0, start = 0;
boolean ltr = true; // target.getComponentOrientation().isLeftToRight();
Dimension uniform = getUniformDimension(target);
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = m.getPreferredSize();
if (isWidthUniform)
d.width = uniform.width;
if (isHeightUniform)
d.height = uniform.height;
m.setSize(d.width, d.height);
if ((x == 0) || ((x + d.width) <= maxwidth)) {
if (x > 0) {
x += getHgap();
}
x += d.width;
rowh = Math.max(rowh, d.height);
} else {
moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, i, ltr);
x = d.width;
y += getVgap() + rowh;
rowh = d.height;
start = i;
}
}
}
moveComponentsHorizontal(target, insets.left + getHgap(), y, maxwidth - x, rowh, start, nmembers, ltr);
}
}
/**
* Centers the elements in the specified row, if there is any slack.
*
* @param target the component which needs to be moved
* @param x the x coordinate
* @param y the y coordinate
* @param width the width dimensions
* @param height the height dimensions
* @param rowStart the beginning of the row
* @param rowEnd the the ending of the row
*/
private void moveComponentsHorizontal(Container target, int x, int y, int width, int height, int rowStart,
int rowEnd, boolean ltr) {
synchronized (target.getTreeLock()) {
switch (getAlignment()) {
case LEFT:
x += ltr ? 0 : width;
break;
case CENTER:
x += width / 2;
break;
case RIGHT:
x += ltr ? width : 0;
break;
//1.2 case LEADING:
//1.2 break;
//1.2 case TRAILING:
//1.2 x += width;
//1.2 break;
}
for (int i = rowStart; i < rowEnd; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
if (ltr) {
m.setLocation(x, y + (height - m.getBounds().height) / 2);
} else {
m.setLocation(target.getBounds().width - x - m.getBounds().width,
y + (height - m.getBounds().height) / 2);
}
x += m.getBounds().width + getHgap();
}
}
}
}
/**
* Lays out the container. This method lets each component take its preferred
* size by reshaping the components in the target container in order to satisfy
* the constraints of this BetterFlowLayout object.
*
* @param target the specified component being laid out.
* @see Container
* @see java.awt.Container#doLayout
*/
protected void layoutContainerVertical(Container target) {
synchronized (target.getTreeLock()) {
Insets insets = target.getInsets();
int maxheight = target.getBounds().height - (insets.top + insets.bottom + getVgap() * 2);
int nmembers = target.getComponentCount();
int y = 0, x = insets.left + getHgap();
int colw = 0, start = 0;
Dimension uniform = getUniformDimension(target);
for (int i = 0; i < nmembers; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
Dimension d = m.getPreferredSize();
if (isWidthUniform)
d.width = uniform.width;
if (isHeightUniform)
d.height = uniform.height;
m.setSize(d.width, d.height);
if ((y == 0) || ((y + d.height) <= maxheight)) {
if (y > 0) {
y += getVgap();
}
y += d.height;
colw = Math.max(colw, d.width);
} else {
moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, i);
y = d.height;
x += getHgap() + colw;
colw = d.width;
start = i;
}
}
}
moveComponentsVertical(target, x, insets.top + getVgap(), colw, maxheight - y, start, nmembers);
}
}
/**
* Centers the elements in the specified row, if there is any slack.
*
* @param target the component which needs to be moved
* @param x the x coordinate
* @param y the y coordinate
* @param width the width dimensions
* @param height the height dimensions
* @param colStart the beginning of the column
* @param colEnd the the ending of the column
*/
private void moveComponentsVertical(Container target, int x, int y, int width, int height, int colStart,
int colEnd) {
synchronized (target.getTreeLock()) {
switch (getAlignment()) {
case TOP:
y += 0;
break;
case CENTER_VERTICAL:
y += (height / 2); // - preferredLayoutSize( target ).height ) / 2 );
break;
case BOTTOM:
y += height;
break;
}
for (int i = colStart; i < colEnd; i++) {
Component m = target.getComponent(i);
if (m.isVisible()) {
m.setLocation(x + (width - m.getBounds().width) / 2, y);
// m.setLocation(x, y );
// m.setSize( width, m.getBounds().height ); //!
y += m.getBounds().height + getVgap();
}
}
}
}
/**
* Returns a dimension representing the maximum preferred height and width of
* all the components in the container.
*
* @param target the container to scan.
* @return a dimension containing the maximum values.
*/
protected Dimension getUniformDimension(Container target) {
Component m = null;
Dimension preferred = null;
int maxWidth = 0, maxHeight = 0;
int nmembers = target.getComponentCount();
for (int i = 0; i < nmembers; i++) {
m = target.getComponent(i);
if (m.isVisible()) {
preferred = m.getPreferredSize();
maxWidth = Math.max(maxWidth, preferred.width);
maxHeight = Math.max(maxHeight, preferred.height);
}
}
return new Dimension(maxWidth, maxHeight);
}
/**
* Returns a string representation of this BetterFlowLayout object
* and its values.
*
* @return a string representation of this layout.
*/
public String toString() {
String str = "";
switch (getAlignment()) {
case TOP:
str = ",align=top";
break;
case CENTER_VERTICAL:
str = ",align=vertical";
break;
case BOTTOM:
str = ",align=bottom";
break;
default:
return super.toString();
}
return getClass().getName() + "[hgap=" + getHgap() + ",vgap=" + getVgap() + str + "]";
}
}