StrutLayout.java
/*
$Id: StrutLayout.java,v 1.17 1998/07/04 03:29:29 matt Exp $
A Java AWT layout manager.
Copyright (c) 1998 Matthew Phillips <mpp@ozemail.com.au>.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
*/
package matthew.awt;
import java.awt.*;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Hashtable;
/**
StrutLayout is an AWT layout manager that lays out components by
logically connecting them with <i>struts</i>. Each StrutLayout has
a <i>root</i> component which any number of <i>child</i> components
may connect to. Each child component may have also have child
components, and so on.
<p>Each component in a StrutLayout may also have internal
<i>springs</i> associated with it. These springs can be used to
make the component expand horizontally and/or vertically to take up
any extra space. The expansion takes into account any child
components, ensuring they are not pushed off the edge of the layout
area.
<p>Additionally, each component may be a member of a <i>size
group</i> which allows sets of components to maintain the same
horizontal and/or vertical dimensions as the largest component in
the group.
<p><img src="images/example.gif">
<p>NOTE: although AWT layout managers are in theory able to layout
more than container simultaneously, StrutLayout will get very
confused if you try to use it like this. For now, you need to use
one instance of StrutLayout per container.
@version 1.1 ($Revision: 1.17 $)
@author Matthew Phillips <a href="mailto:mpp@ozemail.com.au">mpp@ozemail.com.au</a>
@see StrutLayout.StrutConstraint
@see StrutLayout.VectorConstraint
@see #setSprings
@see #createSizeGroup
*/
public class StrutLayout implements LayoutManager2
{
/*-- rectangle positions ----------------------------------------------*/
/** Represents the top left corner of a rectangle. */
public static final int TOP_LEFT = 0;
/** Represents the middle of the top side of a rectangle. */
public static final int MID_TOP = 1;
/** Represents the top right corner of a rectangle. */
public static final int TOP_RIGHT = 2;
/** Represents the middle of the right side of a rectangle. */
public static final int MID_RIGHT = 3;
/** Represents the bottom right corner of a rectangle. */
public static final int BOTTOM_RIGHT = 4;
/** Represents the middle of the bottom side of a rectangle. */
public static final int MID_BOTTOM = 5;
/** Represents the bottom left corner of a rectangle. */
public static final int BOTTOM_LEFT = 6;
/** Represents the middle of the left side of a rectangle. */
public static final int MID_LEFT = 7;
/** Represents the center of a rectangle. */
public static final int CENTER = 8;
/*-- directions -------------------------------------------------------*/
/** Represents the direction towards the top of the screen. */
public static final int NORTH = 100;
/** Represents the direction towards the bottom of the screen. */
public static final int SOUTH = 101;
/** Represents the direction towards the right of the screen. */
public static final int EAST = 102;
/** Represents the direction towards the left of the screen. */
public static final int WEST = 103;
/** Represents the direction towards the top right of the screen. */
public static final int NORTH_EAST = 104;
/** Represents the direction towards the bottom right of the screen. */
public static final int SOUTH_EAST = 105;
/** Represents the direction towards the bottom left of the screen. */
public static final int SOUTH_WEST = 106;
/** Represents the direction towards top left of the screen. */
public static final int NORTH_WEST = 107;
/*-- springs ----------------------------------------------------------*/
/** Specifies a non-existent spring. */
public static final int SPRING_NONE = 0;
/** Specifies a spring that expands horizontally. */
public static final int SPRING_HORIZ = 1;
/** Specifies a spring that expands vertically. */
public static final int SPRING_VERT = 2;
/** Specifies a spring that expands both horizontally and vertically. */
public static final int SPRING_BOTH = SPRING_HORIZ + SPRING_VERT;
/*-- size groups ------------------------------------------------------*/
/** Specifies a member of a size group whose width is tied to the
group's width. */
public static final int SIZE_WIDTH = 1;
/** Specifies a member of a size group whose height is tied to the
group's height. */
public static final int SIZE_HEIGHT = 2;
/** Specifies a member of a size group whose width and height are
tied to the group's size. */
public static final int SIZE_BOTH = SIZE_WIDTH + SIZE_HEIGHT;
/** Specifies a member of a size group who participates in setting
the group's size but which does not change size itself. */
public static final int SIZE_NONE = 0;
/*-- SizeGroup --------------------------------------------------------*/
/**
Represents a group of components who have their width and/or
height tied to the size of the group. The size of the group is
the width of the largest component and the height of the tallest
component.
<p>Example:
<pre>
...
StrutLayout.SizeGroup buttonGroup = strutLayout.createSizeGroup ();
buttonGroup.add (editButton, StrutLayout.SIZE_WIDTH);
buttonGroup.add (removeButton, StrutLayout.SIZE_WIDTH);
...
</pre>
@see StrutLayout#createSizeGroup
@see #add
@see StrutLayout#SIZE_NONE
@see StrutLayout#SIZE_HEIGHT
@see StrutLayout#SIZE_WIDTH
@see StrutLayout#SIZE_BOTH
*/
public final class SizeGroup
{
protected SizeGroup ()
{
}
/**
Sets width and height of the components in this group as
defined by their sizeMode.
*/
protected void applySizeChange (int width, int height)
{
Enumeration e = components.elements ();
while (e.hasMoreElements ())
{
ComponentInfo componentInfo = (ComponentInfo)e.nextElement ();
if (width > componentInfo.width &&
(componentInfo.sizeGroup.sizeMode & SIZE_WIDTH) != 0)
{
componentInfo.width = width;
}
if (height > componentInfo.height &&
(componentInfo.sizeGroup.sizeMode & SIZE_HEIGHT) != 0)
{
componentInfo.height = height;
}
}
maxWidth = Math.max (width, maxWidth);
maxHeight = Math.max (width, maxHeight);
}
/**
Adds a component to this group using <i>sizeMode</i> to
determine which dimensions are tied to the group. If the
component is already part of a size group this has no effect.
@param component The component to be added. The component must
have been already added to the layout.
@param sizeMode Determines which dimensions of the component
are tied to this group. Values may be SIZE_NONE, SIZE_WIDTH,
SIZE_HEIGHT or SIZE_BOTH. Using SIZE_NONE allows the component
to participate in setting the max width and height but ensures
the component itself is never changed.
@see #remove
@see StrutLayout#SIZE_NONE
@see StrutLayout#SIZE_HEIGHT
@see StrutLayout#SIZE_WIDTH
@see StrutLayout#SIZE_BOTH
*/
public void add (Component component, int sizeMode)
{
try
{
ComponentInfo componentInfo =
(ComponentInfo)componentInfoHash.get (component);
if (componentInfo.sizeGroup != null)
return;
componentInfo.sizeGroup = new SizeGroupInfo (this, sizeMode);
components.addElement (componentInfo);
} catch (NullPointerException exception)
{
}
}
/**
Removes a component from the group. If the component is not a
member of this group this has no effect.
@param component The component to remove.
@see #add
*/
public void remove (Component component)
{
try
{
ComponentInfo componentInfo =
(ComponentInfo)componentInfoHash.get (component);
if (componentInfo.sizeGroup.group == this)
{
components.removeElement (componentInfo);
componentInfo.sizeGroup = null;
}
} catch (NullPointerException exception)
{
// this may be because component is not in the layout or if
// its not in this group.
}
}
protected Vector components = new Vector ();
protected int maxWidth = 0;
protected int maxHeight = 0;
}
/*-- Strut ------------------------------------------------------------*/
/**
Represents a strut going from a parent component to a child (see
StrutLayout.StrutConstraint for a description of struts). This
class is used in conjunction with addStruts () as an alternative
to a sequence of addLayoutComponent (component, new
StrutConstraint (...)) statements.
<p>Example:
<pre>
StrutLayout.Strut struts [] =
{new StrutLayout.Strut (nameLabel, nameField,
StrutLayout.MID_RIGHT, StrutLayout.MID_LEFT,
StrutLayout.EAST),
new StrutLayout.Strut (ageField, nameField,
StrutLayout.BOTTOM_LEFT, StrutLayout.TOP_LEFT,
StrutLayout.SOUTH)
};
Panel panel = new Panel ();
StrutLayout strutLayout = new StrutLayout ();
panel.setLayout (strutLayout);
panel.add (rootComponent);
StrutLayout.addStruts (panel, struts);
</pre>
@see StrutLayout.StrutConstraint
@see StrutLayout#addStruts
*/
public static final class Strut
{
/**
Creates a strut going from a parent component to a child
component with a default length
@param parent The parent component.
@param child The child component.
@param fromConnector The connector on the parent to attach the
strut to (eg. StrutLayout.TOP_RIGHT).
@param toConnector The connector on the child to attach the
strut to (eg. StrutLayout.TOP_LEFT).
@param direction The direction of the strut. One of
StrutLayout.NORTH, StrutLayout.SOUTH, StrutLayout.EAST or
StrutLayout.WEST.
@see StrutLayout.Strut#Strut(java.awt.Component, java.awt.Component, int, int, int, int)
@see StrutLayout.StrutConstraint
@see StrutLayout#getDefaultStrutLength
*/
public Strut (Component parent, Component child,
int fromConnector, int toConnector,
int direction)
{
this.parent = parent;
this.child = child;
this.fromConnector = fromConnector;
this.toConnector = toConnector;
this.direction = direction;
this.length = StrutLayout.getDefaultStrutLength ();
}
/**
Creates a strut going from a parent component to a child
component.
@param parent The parent component.
@param child The child component.
@param fromConnector The connector on the parent to attach the
strut to (eg. StrutLayout.TOP_RIGHT).
@param toConnector The connector on the child to attach the
strut to (eg. StrutLayout.TOP_LEFT).
@param direction The direction of the strut. One of
StrutLayout.NORTH, StrutLayout.SOUTH, StrutLayout.EAST or
StrutLayout.WEST.
@param length The length (in pixels) of the strut.
@see StrutLayout.Strut#Strut(java.awt.Component, java.awt.Component, int, int, int)
@see StrutLayout.StrutConstraint
*/
public Strut (Component parent, Component child,
int fromConnector, int toConnector,
int direction, int length)
{
this.parent = parent;
this.child = child;
this.fromConnector = fromConnector;
this.toConnector = toConnector;
this.direction = direction;
this.length = length;
}
public Component parent, child;
public int fromConnector, toConnector;
public int direction;
public int length;
}
/*-- StrutConstraint --------------------------------------------------*/
/**
Represents a strut constraint placed on the location of a
component. Each component has nine logical connector points which
may be connected by struts to other components.
<p><img src="images/connectors.gif"></p>
Each strut goes from a <i>parent</i> component to a <i>child</i>
and has a set direction and length.
<p>Example:
<pre>
panel.add (nameField, new StrutLayout.StrutConstraint
(nameLabel, StrutLayout.MID_RIGHT, StrutLayout.MID_LEFT,
StrutLayout.EAST));
</pre>
@see StrutLayout#addLayoutComponent(java.awt.Component, java.lang.Object)
@see StrutLayout.Strut
*/
public static final class StrutConstraint
{
/**
Create a strut between connection points on a parent and a
child component.
@param parent The component the connector starts from.
@param fromConnector The connector point on the parent that the
strut is connected to (eg. TOP_LEFT).
@param toConnector The connector point on the child that the
strut is connected to (eg. TOP_RIGHT).
@param direction The direction that the strut points
(eg. NORTH).
@param length The length (in pixels) of the strut along the x
and/or y axes. Thus a NORTH_WEST strut of length 5 will
actually extend 7 pixels diagonally, while a NORTH strut will
extend 5 pixels vertically.
@see StrutLayout.StrutConstraint#StrutConstraint(java.awt.Component, int, int, int)
@see StrutLayout.VectorConstraint
@see StrutLayout#TOP_LEFT
@see StrutLayout#MID_TOP
@see StrutLayout#TOP_RIGHT
@see StrutLayout#MID_RIGHT
@see StrutLayout#BOTTOM_RIGHT
@see StrutLayout#MID_BOTTOM
@see StrutLayout#BOTTOM_LEFT
@see StrutLayout#MID_LEFT
@see StrutLayout#CENTER
@see StrutLayout#NORTH
@see StrutLayout#SOUTH
@see StrutLayout#EAST
@see StrutLayout#WEST
@see StrutLayout#NORTH_WEST
@see StrutLayout#NORTH_EAST
@see StrutLayout#SOUTH_WEST
@see StrutLayout#SOUTH_EAST
*/
public StrutConstraint (Component parent,
int fromConnector, int toConnector,
int direction, int length)
{
this.parent = parent;
this.fromConnector = fromConnector;
this.toConnector = toConnector;
this.length = length;
this.direction = direction;
}
/**
Create a strut between connection points on a parent and a
child component. The horizontal and/or vertical lengths of the
strut are set to the default defined by
getDefaultStrutLength().
@param parent The component the connector starts from.
@param fromConnector The connector point on the parent
component that the strut is connected to (eg. TOP_LEFT).
@param toConnector The connector point on the child component
that the strut is connected to (eg. TOP_RIGHT).
@param direction The direction that the strut extends
(eg. SOUTH).
@see StrutLayout.StrutConstraint#StrutConstraint(java.awt.Component, int, int, int, int)
@see StrutLayout#getDefaultStrutLength
@see StrutLayout.VectorConstraint
@see StrutLayout#TOP_LEFT
@see StrutLayout#MID_TOP
@see StrutLayout#TOP_RIGHT
@see StrutLayout#MID_RIGHT
@see StrutLayout#BOTTOM_RIGHT
@see StrutLayout#MID_BOTTOM
@see StrutLayout#BOTTOM_LEFT
@see StrutLayout#MID_LEFT
@see StrutLayout#CENTER
@see StrutLayout#NORTH
@see StrutLayout#SOUTH
@see StrutLayout#EAST
@see StrutLayout#WEST
@see StrutLayout#NORTH_WEST
@see StrutLayout#NORTH_EAST
@see StrutLayout#SOUTH_WEST
@see StrutLayout#SOUTH_EAST
*/
public StrutConstraint (Component parent,
int fromConnector, int toConnector,
int direction)
{
this.parent = parent;
this.fromConnector = fromConnector;
this.toConnector = toConnector;
this.length = defaultStrutLength;
this.direction = direction;
}
public Component parent;
public int fromConnector, toConnector;
public int length;
public int direction;
}
/*-- VectorConstraint -------------------------------------------------*/
/**
Represents a strut with arbritrary direction and length.
@see StrutLayout.StrutConstraint
*/
public static final class VectorConstraint
{
/**
Create a strut between connection points on a parent and a
child component. The strut's length and direction are
determined by a vector of horizontal and vertical components.
@param parent The component to attach the child to.
@param fromConnector The connection point on the parent to
attach the strut to (eg. BOTTOM_LEFT).
@param toConnector The connection point on the child to attach
the strut to (eg. TOP_LEFT).
@param hdelta The amount in the horizontal (x) direction that
the strut extends (eg. -<i>n</i> implies left <i>n</i> pixels).
@param vdelta The amount in the vertical (y) direction that the
strut extends (eg. -<i>n</i> implies up <i>n</i> pixels).
@see StrutLayout.StrutConstraint
*/
public VectorConstraint (Component parent, int fromConnector,
int toConnector, int hdelta, int vdelta)
{
this.parent = parent;
this.fromConnector = fromConnector;
this.toConnector = toConnector;
this.hdelta = hdelta;
this.vdelta = vdelta;
}
public Component parent;
public int fromConnector;
public int toConnector;
public int hdelta;
public int vdelta;
}
/*-- StrutLayout ------------------------------------------------------*/
public StrutLayout ()
{
}
/**
Sets the alignment of the layout area within the container (the
default is CENTER). This only has effect when the components
managed by the layout do not use all the space in the container.
@param alignment The alignment of the layout area within the
container. One of TOP_LEFT, MID_TOP, TOP_RIGHT, MID_RIGHT,
BOTTOM_RIGHT, MID_BOTTOM, BOTTOM_LEFT, MID_LEFT or CENTER (the
default).
@see #getAlignment
*/
public void setAlignment (int alignment)
{
this.alignment = alignment;
invalid = true;
}
/**
Returns the current alignment the layout area within the
container (see setAlignment() for details).
@return The current alignment of components within the layout
area.
@see #setAlignment
*/
public int getAlignment ()
{
return alignment;
}
/**
Sets internal springs on a component which act to expand the
component horizontally and/or vertically to fill empty space.
Child components are pushed as far down/right as they can go
without pushing them off the container.
<p><b>NOTE</b>: while child components are protected from being
covered by their sprung parent components, this does not prevent
others not connected to the parent from being potentially
obscured. It is up to you to ensure the layout rules do not
allow this.
<p>Example:
<pre>
strutLayout.setSprings (tablePane, StrutLayout.SPRING_BOTH);
</pre>
@param component The component to add the spring to. The
component must already be a part of the layout
@param springs The spring(s) to add to the component. One of
SPRING_NONE, SPRING_HORIZ, SPRING_VERT or SPRING_BOTH.
@see StrutLayout#SPRING_NONE
@see StrutLayout#SPRING_VERT
@see StrutLayout#SPRING_HORIZ
@see StrutLayout#SPRING_BOTH
*/
public void setSprings (Component component, int springs)
{
try
{
ComponentInfo componentInfo =
(ComponentInfo)componentInfoHash.get (component);
if (springs == SPRING_NONE)
componentInfo.springInfo = null;
else
componentInfo.springInfo = new SpringInfo (springs);
} catch (NullPointerException exception)
{
}
}
/**
Creates a new group of components whose sizes are tied together.
See StrutLayout.SizeGroup for a full description.
@return A new size group object.
@see StrutLayout.SizeGroup
@see #getSizeGroup
*/
public SizeGroup createSizeGroup ()
{
return new SizeGroup ();
}
/**
Returns the size group that a component is a member of.
@param component The component to be queried.
@return The size group the component is a member of or null if
the component is not a member of a group.
@see StrutLayout.SizeGroup
@see #createSizeGroup
*/
public SizeGroup getSizeGroup (Component component)
{
try
{
ComponentInfo componentInfo =
(ComponentInfo)componentInfoHash.get (component);
return componentInfo.sizeGroup.group;
} catch (NullPointerException exception)
{
return null;
}
}
/**
Returns the current default length for struts. This setting is
global to all instances of StrutLayout.
@return Returns the current default length.
@see #setDefaultStrutLength
@see StrutLayout.StrutConstraint
@see StrutLayout.Strut
*/
public static int getDefaultStrutLength ()
{
return defaultStrutLength;
}
/**
Sets the current default length for struts. This setting is
global to all instances of StrutLayout.
@see #getDefaultStrutLength
@see StrutLayout.StrutConstraint
@see StrutLayout.Strut
*/
public static void setDefaultStrutLength (int length)
{
defaultStrutLength = length;
}
/**
Fixes the preferred size for a component regardless of what its
getPreferredSize () method returns. A component's preferred size
can also be fixed in this way by sizing it to a non-zero area
(via setSize ()) before adding it to the layout.
@param component The component whose preferred size is to be
fixed.
@param preferredSize The new preferred size of the
component. If this is null, then the component's preferred size
reverts to the result of its getPreferredSize () method.
@see #addLayoutComponent(java.awt.Component, java.lang.Object)
*/
public void setPreferredSize (Component component,
Dimension preferredSize)
{
try
{
ComponentInfo componentInfo =
(ComponentInfo)componentInfoHash.get (component);
if (preferredSize == null)
componentInfo.preferredSize = null;
else
componentInfo.preferredSize = new Dimension (preferredSize);
invalid = true;
} catch (NullPointerException exception)
{
}
}
/**
Adds a number of struts to the layout at once. This is a
convenient shortcut which is equivalent to a series of
Component.add (...) calls.
@param struts The struts to be added.
@see StrutLayout.Strut
@see #addLayoutComponent(java.awt.Component, java.lang.Object)
*/
public static void addStruts (Container container,
Strut [] struts)
{
for (int i = 0; i < struts.length; i++)
{
Strut strut = struts [i];
container.add
(strut.child, new StrutConstraint
(strut.parent,
strut.fromConnector,
strut.toConnector,
strut.direction, strut.length));
}
}
/*-- LayoutManager2 implementation ------------------------------------*/
/**
Does nothing. Use addLayoutComponent (Component, Object)
instead.
@see #addLayoutComponent(java.awt.Component, java.lang.Object)
*/
public void addLayoutComponent (String name, Component component)
{
// does nothing
}
/**
Removes a component from a layout. This also removes all child
components connected by struts.
@param component The component to remove.
@see #addLayoutComponent(java.awt.Component, java.lang.Object)
*/
public void removeLayoutComponent (Component component)
{
try
{
ComponentInfo componentInfo =
(ComponentInfo)componentInfoHash.get (component);
removeComponentInfo (componentInfo);
if (componentInfo == rootComponentInfo)
rootComponentInfo = null;
invalid = true;
} catch (NullPointerException exception)
{
}
}
/**
Adds a component to the layout, possibly using a strut as the
constraint. Using null as the constraint indicates the component
is the root component (of which there may only be one).
<p><b>NOTE</b>: If the component has a non-zero size when added,
this size will be used as its preferred size rather than the
result of its getPreferredSize () method.
@param component The component to add.
@param constraintObject The constraint to use when laying out the
component. This may be an instance of either
StrutLayout.StrutConstraint or StrutLayout.VectorConstraint or
null for the root component.
@see StrutLayout.StrutConstraint
@see StrutLayout.VectorConstraint
@see #setPreferredSize
*/
public void addLayoutComponent (Component component,
Object constraintObject)
{
if (constraintObject == null)
doAddRootComponent (component);
else if (constraintObject instanceof StrutConstraint)
{
StrutConstraint strutConstraint = (StrutConstraint)constraintObject;
Point vector = getDeltaForDirection (strutConstraint.direction);
doAddStrutComponent (strutConstraint.parent, component,
strutConstraint.fromConnector,
strutConstraint.toConnector,
vector.x * strutConstraint.length,
vector.y * strutConstraint.length);
} else if (constraintObject instanceof VectorConstraint)
{
VectorConstraint vectorConstraint = (VectorConstraint)constraintObject;
doAddStrutComponent (vectorConstraint.parent, component,
vectorConstraint.fromConnector,
vectorConstraint.toConnector,
vectorConstraint.hdelta, vectorConstraint.vdelta);
}
}
public float getLayoutAlignmentX (Container parent)
{
return (float)0.0;
}
public float getLayoutAlignmentY (Container parent)
{
return (float)0.0;
}
public Dimension maximumLayoutSize (Container parent)
{
return maximumLayoutSize;
}
public Dimension minimumLayoutSize (Container parent)
{
return preferredLayoutSize (parent);
}
public Dimension preferredLayoutSize (Container parent)
{
if (invalid)
{
recalculateLayout (parent);
invalid = false;
}
return new Dimension (preferredLayoutSize);
}
public void layoutContainer (Container parent)
{
if (invalid)
{
recalculateLayout (parent);
invalid = false;
}
Enumeration e = componentInfoHash.elements ();
while (e.hasMoreElements ())
{
ComponentInfo componentInfo = (ComponentInfo)e.nextElement ();
componentInfo.component.setBounds (componentInfo);
}
}
public void invalidateLayout (Container parent)
{
invalid = true;
}
/*-- layout calculation -----------------------------------------------*/
/**
Completely recalculates the layout information.
*/
protected void recalculateLayout (Container container)
{
Rectangle boundingBox;
Insets insets = container.getInsets ();
Dimension layoutArea = container.getSize ();
layoutArea.width -= insets.left + insets.right;
layoutArea.height -= insets.top + insets.bottom;
if (rootComponentInfo == null)
{
preferredLayoutSize = new Dimension (0, 0);
return;
}
resetInfo ();
// size components the way they wish
assignPreferredSizes ();
applySizeGroupings ();
// first layout pass
boundingBox = assignRelativePositions ();
// apply springs and possible second layout pass
if (evaluateSprings (layoutArea))
{
boundingBox = assignRelativePositions ();
}
Point offset = calculateAlignmentOffset (layoutArea, boundingBox);
// adjust positions for insets and alignment
translateComponents (insets.left + offset.x, insets.top + offset.y);
preferredLayoutSize =
new Dimension (boundingBox.width + insets.left + insets.right,
boundingBox.height + insets.top + insets.bottom);
}
/**
Resets the SizeGroup maxHeight and maxWidth variables and the
SpringInfo horizExtent and vertExtent variables to their initial
value of 0.
*/
protected void resetInfo ()
{
Enumeration e = componentInfoHash.elements ();
while (e.hasMoreElements ())
{
ComponentInfo componentInfo = (ComponentInfo)e.nextElement ();
if (componentInfo.sizeGroup != null)
{
componentInfo.sizeGroup.group.maxHeight = 0;
componentInfo.sizeGroup.group.maxWidth = 0;
}
if (componentInfo.springInfo != null)
{
componentInfo.springInfo.horizExtent = 0;
componentInfo.springInfo.vertExtent = 0;
}
}
}
/**
Sizes components to their preferred size. Also collects max
height and width information for size groups.
*/
protected void assignPreferredSizes ()
{
Enumeration e = componentInfoHash.elements ();
while (e.hasMoreElements ())
{
ComponentInfo componentInfo = (ComponentInfo)e.nextElement ();
Dimension preferredSize;
if (componentInfo.preferredSize == null)
preferredSize = componentInfo.component.getPreferredSize ();
else
preferredSize = componentInfo.preferredSize;
componentInfo.width = preferredSize.width;
componentInfo.height = preferredSize.height;
if (componentInfo.sizeGroup != null)
{
SizeGroup group = componentInfo.sizeGroup.group;
group.maxWidth = Math.max (group.maxWidth, componentInfo.width);
group.maxHeight = Math.max (group.maxHeight, componentInfo.height);
}
}
}
/**
Assigns component positions starting from root, adjusting for the
case where child components extend above or to the left of the
root.
@return The bounding box for all components.
@see #assignRelativePositions(StrutLayout.ComponentInfo)
*/
protected Rectangle assignRelativePositions ()
{
// root component starts at location (0, 0)
rootComponentInfo.x = 0;
rootComponentInfo.y = 0;
Rectangle boundingBox = assignRelativePositions (rootComponentInfo);
// adjust for possible negative shift in boundingBox
if (boundingBox.x < 0 || boundingBox.y < 0)
{
translateComponents (-boundingBox.x, -boundingBox.y);
}
return boundingBox;
}
/**
Recursively assign components relative positions based on their
strut constraints starting from root.
@param root The component to start the layout at.
@return The bounding box containing the root and all its child
components.
*/
protected Rectangle assignRelativePositions (ComponentInfo root)
{
Rectangle boundingBox = new Rectangle (root);
if (root.struts != null)
{
Enumeration e = root.struts.elements ();
while (e.hasMoreElements ())
{
StrutConnection strut = (StrutConnection)e.nextElement ();
ComponentInfo child = strut.child;
Point point = getOffsetForConnector (root, strut.from);
point.x += root.x;
point.y += root.y;
point.x += strut.hdelta;
point.y += strut.vdelta;
Point toDelta = getOffsetForConnector (child, strut.to);
child.x = point.x - toDelta.x;
child.y = point.y - toDelta.y;
Rectangle childBounds = assignRelativePositions (child);
boundingBox.add (childBounds);
if (root.springInfo != null)
{
updateSpringInfo (root, strut.from, childBounds);
}
}
}
return boundingBox;
}
/**
Updates the horizontal and vertical extent settings for a
component. childBounds is the bounding box of a child tree
connected to the fromConnector connection point of a parent
component. This routine checks whether the child is affected by
horizontal and/or vertical expansion of the parent and, if so,
updates the vertical and horizontal child extent settings for the
component's springInfo.
*/
protected void updateSpringInfo (ComponentInfo componentInfo,
int fromConnector,
Rectangle childBounds)
{
SpringInfo springInfo = componentInfo.springInfo;
boolean affectedHoriz = false;
boolean affectedVert = false;
// decide which expansion directions will affect child
switch (fromConnector)
{
case MID_TOP:
case TOP_RIGHT:
affectedHoriz = true;
break;
case MID_RIGHT:
case BOTTOM_RIGHT:
case MID_BOTTOM:
case CENTER:
affectedHoriz = true;
affectedVert = true;
break;
case BOTTOM_LEFT:
case MID_LEFT:
affectedVert = true;
break;
}
// update horizontal and vertical extent information
if (affectedHoriz)
{
springInfo.horizExtent =
Math.max (childBounds.x + childBounds.width - componentInfo.x,
springInfo.horizExtent);
}
if (affectedVert)
{
springInfo.vertExtent =
Math.max (childBounds.y + childBounds.height - componentInfo.y,
springInfo.vertExtent);
}
}
/**
Evaluate spring settings, expanding components where possible.
@param layoutArea The max area available for layout.
*/
protected boolean evaluateSprings (Dimension layoutArea)
{
boolean changed = false;
Enumeration e = componentInfoHash.elements ();
while (e.hasMoreElements ())
{
ComponentInfo componentInfo = (ComponentInfo)e.nextElement ();
if (componentInfo.springInfo != null)
{
SpringInfo springInfo = componentInfo.springInfo;
int horizDelta = 0;
int vertDelta = 0;
// check for horizontal spring
if ((springInfo.springs & SPRING_HORIZ) != 0)
{
// check if there are no child components that are affected
// horizontally
if (springInfo.horizExtent == 0)
springInfo.horizExtent = componentInfo.width;
// compute horizontal expansion
horizDelta = layoutArea.width -
(componentInfo.x + springInfo.horizExtent);
if (horizDelta > 0)
{
componentInfo.width += horizDelta;
changed = true;
}
}
// check for vertical spring
if ((springInfo.springs & SPRING_VERT) != 0)
{
// check if there are no child components that are affected
// vertically
if (springInfo.vertExtent == 0)
springInfo.vertExtent = componentInfo.height;
// compute vertical expansion
vertDelta = layoutArea.height -
(springInfo.vertExtent + componentInfo.y);
if (vertDelta > 0)
{
componentInfo.height += vertDelta;
changed = true;
}
}
if (componentInfo.sizeGroup != null &&
(vertDelta > 0 || horizDelta > 0))
{
componentInfo.sizeGroup.group.applySizeChange
(componentInfo.width, componentInfo.height);
}
}
}
return changed;
}
/**
Apply any size group constraints.
*/
protected void applySizeGroupings ()
{
Enumeration e = componentInfoHash.elements ();
while (e.hasMoreElements ())
{
ComponentInfo componentInfo = (ComponentInfo)e.nextElement ();
if (componentInfo.sizeGroup != null)
{
SizeGroup group = componentInfo.sizeGroup.group;
if ((componentInfo.sizeGroup.sizeMode & SIZE_WIDTH) != 0)
componentInfo.width = group.maxWidth;
if ((componentInfo.sizeGroup.sizeMode & SIZE_HEIGHT) != 0)
componentInfo.height = group.maxHeight;
}
}
}
/**
Translate all component locations by xdelta, ydelta.
*/
protected void translateComponents (int xdelta, int ydelta)
{
Enumeration e = componentInfoHash.elements ();
while (e.hasMoreElements ())
{
ComponentInfo componentInfo = (ComponentInfo)e.nextElement ();
componentInfo.x += xdelta;
componentInfo.y += ydelta;
}
}
/**
Returns an offset that will align boundingBox within the given
layoutArea according to the current alignment.
*/
protected Point calculateAlignmentOffset (Dimension layoutArea,
Rectangle boundingBox)
{
Point offset = new Point ();
// horizontal offset
switch (alignment)
{
case MID_TOP:
case CENTER:
case MID_BOTTOM:
offset.x = (layoutArea.width - boundingBox.width) / 2; break;
case TOP_RIGHT:
case MID_RIGHT:
case BOTTOM_RIGHT:
offset.x = layoutArea.width - boundingBox.width; break;
}
// vertical offset
switch (alignment)
{
case MID_LEFT:
case CENTER:
case MID_RIGHT:
offset.y = (layoutArea.height - boundingBox.height) / 2; break;
case BOTTOM_LEFT:
case MID_BOTTOM:
case BOTTOM_RIGHT:
offset.y = layoutArea.height - boundingBox.height; break;
}
// negative values become 0
offset.x = Math.max (0, offset.x);
offset.y = Math.max (0, offset.y);
return offset;
}
/*-- utility methods --------------------------------------------------*/
/**
Returns a vector (as a Point) that can be added to a component's
location to get the location of the specified connector.
@param shape The shape to find the connector offset for. Only
the shape's width and height are used.
@param connector The connector. See the TOP_LEFT, etc constants.
@return The offset vector.
*/
protected static Point getOffsetForConnector (Rectangle shape,
int connector)
{
Point point = new Point ();
switch (connector)
{
case TOP_LEFT:
point.x = 0; point.y = 0;
break;
case MID_LEFT:
point.x = 0; point.y = shape.height / 2;
break;
case BOTTOM_LEFT:
point.x = 0; point.y = shape.height - 1;
break;
case MID_BOTTOM:
point.x = shape.width / 2; point.y = shape.height - 1;
break;
case BOTTOM_RIGHT:
point.x = shape.width - 1; point.y = shape.height - 1;
break;
case MID_RIGHT:
point.x = shape.width - 1; point.y = shape.height / 2;
break;
case TOP_RIGHT:
point.x = shape.width - 1; point.y = 0;
break;
case MID_TOP:
point.x = shape.width / 2; point.y = 0;
break;
case CENTER:
point.x = shape.width / 2; point.y = shape.height / 2;
}
return point;
}
/**
Computes a delta vector that codes for a direction. For example,
getDeltaForDirection (SOUTH_WEST) returns (-1, 1).
*/
protected Point getDeltaForDirection (int direction)
{
Point delta = new Point ();
switch (direction)
{
case NORTH:
delta.y = -1;
break;
case SOUTH:
delta.y = 1;
break;
case EAST:
delta.x = 1;
break;
case WEST:
delta.x = -1;
break;
case NORTH_EAST:
delta.y = -1; delta.x = 1;
break;
case NORTH_WEST:
delta.y = -1; delta.x = -1;
break;
case SOUTH_EAST:
delta.y = 1; delta.x = 1;
break;
case SOUTH_WEST:
delta.y = 1; delta.x = -1;
}
return delta;
}
/**
Does the work of adding a component with a strut constraint to
the layout.
*/
protected void doAddStrutComponent (Component parent, Component child,
int fromConnector, int toConnector,
int hdelta, int vdelta)
{
ComponentInfo componentInfo = new ComponentInfo ();
ComponentInfo parentComponentInfo;
componentInfo.component = child;
if (componentInfoHash.get (child) != null)
{
throw new IllegalArgumentException
("StrutLayout: attempt to add component more than once");
}
try
{
parentComponentInfo =
(ComponentInfo)componentInfoHash.get (parent);
} catch (NullPointerException e)
{
// parent not registered
throw new IllegalArgumentException
("StrutLayout: parent must be added to container before child");
}
if (parentComponentInfo.struts == null)
parentComponentInfo.struts = new Vector ();
parentComponentInfo.struts.addElement
(new StrutConnection (componentInfo, fromConnector,
toConnector, hdelta, vdelta));
componentInfo.parent = parentComponentInfo;
Dimension componentSize = child.getSize ();
if (componentSize.width > 0 || componentSize.height > 0)
componentInfo.preferredSize = componentSize;
componentInfoHash.put (child, componentInfo);
invalid = true;
}
/**
Does the work of adding a root component to the layout.
*/
protected void doAddRootComponent (Component rootComponent)
{
ComponentInfo componentInfo = new ComponentInfo ();
componentInfo.component = rootComponent;
if (rootComponentInfo == null)
rootComponentInfo = componentInfo;
else
{
throw new IllegalArgumentException
("StrutLayout: attempt to add more than one root component");
}
Dimension componentSize = rootComponent.getSize ();
if (componentSize.width > 0 || componentSize.height > 0)
componentInfo.preferredSize = componentSize;
componentInfoHash.put (rootComponent, componentInfo);
invalid = true;
}
/**
Remove the ComponentInfo for a component and all its children.
@param componentInfo The componentInfo to remove.
*/
protected void removeComponentInfo (ComponentInfo componentInfo)
{
componentInfoHash.remove (componentInfo.component);
if (componentInfo.parent != null)
componentInfo.parent.struts.removeElement (componentInfo);
if (componentInfo.struts != null)
{
Enumeration e = componentInfo.struts.elements ();
while (e.hasMoreElements ())
{
StrutConnection strut = (StrutConnection)e.nextElement ();
removeComponentInfo (strut.child);
}
}
}
/*-- implementation classes -------------------------------------------*/
/**
Stores constraint information for a component.
*/
protected static final class ComponentInfo extends Rectangle
{
public ComponentInfo parent;
public Component component;
public Dimension preferredSize;
public Vector struts;
public SizeGroupInfo sizeGroup;
public SpringInfo springInfo;
}
/**
Represents a strut from a parent to a child.
*/
protected static final class StrutConnection
{
public StrutConnection (ComponentInfo child, int from, int to,
int hdelta, int vdelta)
{
this.child = child;
this.from = from;
this.to = to;
this.hdelta = hdelta;
this.vdelta = vdelta;
}
public ComponentInfo child;
public int from, to;
public int hdelta, vdelta;
}
/**
Stores spring constraint information.
*/
protected static final class SpringInfo
{
public SpringInfo (int springs)
{
this.springs = springs;
}
public int springs = SPRING_NONE;
/** The horizontal extent (width) of this component and all
children that will be affected by horizontal expansion. */
public int horizExtent;
/** The vertical extent (height) of this component and all
children that will be affected by vertical expansion. */
public int vertExtent;
}
/**
Stores size group information.
*/
protected static final class SizeGroupInfo
{
public SizeGroupInfo (SizeGroup group, int sizeMode)
{
this.group = group;
this.sizeMode = sizeMode;
}
public SizeGroup group;
public int sizeMode;
}
/*-------------------- data members --------------------*/
protected ComponentInfo rootComponentInfo = null;
protected Hashtable componentInfoHash = new Hashtable ();
protected boolean invalid = true;
protected Dimension preferredLayoutSize;
protected int alignment = CENTER;
protected static int defaultStrutLength = 5;
protected static final Dimension maximumLayoutSize =
new Dimension (Integer.MAX_VALUE, Integer.MAX_VALUE);
}