org.piccolo2d.extras.swing.SwingLayoutNode Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org
* Copyright (c) 1998-2008, University of Maryland
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the
* distribution.
*
* None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
* contributors may be used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.piccolo2d.extras.swing;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.LayoutManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.Iterator;
import javax.swing.JComponent;
import javax.swing.JPanel;
import org.piccolo2d.PNode;
/**
* Uses Swing layout managers to position PNodes.
*
* @author Sam Reid
* @author Chris Malley ([email protected])
*/
public class SwingLayoutNode extends PNode {
private static final long serialVersionUID = 1L;
/*
* How the space allocated by the Swing layout manager is used differs
* depending on Swing component type. The behavior of a default JLabel
* (Anchors.WEST) seems to make the most sense for PNodes.
*/
private static final Anchor DEFAULT_ANCHOR = Anchor.WEST;
/** Container for ProxyComponents. */
private final Container container;
private final PropertyChangeListener propertyChangeListener;
/** Anchor to use when adding child nodes and they don't specify one. */
private Anchor defaultAnchor;
/**
* Construct a SwingLayoutNode that uses FlowLayout.
*/
public SwingLayoutNode() {
this(new FlowLayout());
}
/**
* Constructs a SwingLayoutNode that uses the provided LayoutManager to
* layout its children.
*
* @param layoutManager LayoutManager to use for laying out children. Must
* not be null.
*/
public SwingLayoutNode(final LayoutManager layoutManager) {
this(new JPanel(layoutManager));
}
/**
* Constructs a SwingLayoutNode that lays out its children as though they
* were children of the provided Container.
*
* Whatever LayoutManager is being used by the container will be used when
* laying out nodes.
*
* @param container Container in which child nodes will effectively be laid
* out
*/
public SwingLayoutNode(Container container) {
this.container = container;
propertyChangeListener = new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent event) {
final String propertyName = event.getPropertyName();
if (isLayoutProperty(propertyName)) {
updateContainerLayout();
}
}
};
defaultAnchor = DEFAULT_ANCHOR;
}
/**
* Sets the default anchor. If no anchor is specified when a node is added,
* then the default anchor determines where the node is positioned in the
* space allocated by the Swing layout manager.
*
* @param anchor anchor to use when a node is added but its anchor is not
* specified
*/
public void setAnchor(final Anchor anchor) {
this.defaultAnchor = anchor;
}
/**
* Returns the anchor being used by this LayoutManager.
*
* @return anchor currently being used when laying out children.
*/
public Anchor getAnchor() {
return defaultAnchor;
}
/**
* Some Swing layout managers (like BoxLayout) require a reference to the
* proxy Container.
*
* For example:
* SwingLayoutNode layoutNode = new SwingLayoutNode();
* layoutNode.setLayout( new BoxLayout( layoutNode.getContainer(), BoxLayout.Y_AXIS ) );
*
*
* @return container in which children will logically be laid out in
*/
public Container getContainer() {
return container;
}
/**
* Adds a child at the specified index. Like Swing, bad things can happen if
* the type of the constraints isn't compatible with the layout manager.
*
* @param index 0 based index at which to add the child
* @param child child to be added
* @param constraints constraints the layout manager uses when laying out
* the child
* @param childAnchor specifies the location from which layout takes place
*/
public void addChild(final int index, final PNode child, final Object constraints, final Anchor childAnchor) {
/*
* NOTE: This must be the only super.addChild call that we make in our
* entire implementation, because all PNode.addChild methods are
* implemented in terms of this one. Calling other variants of
* super.addChild will incorrectly invoke our overrides, resulting in
* StackOverflowException.
*/
super.addChild(index, child);
addProxyComponent(child, constraints, childAnchor);
}
/** {@inheritDoc} */
public void addChild(final int index, final PNode child) {
addChild(index, child, null, defaultAnchor);
}
/**
* Adds a child at the specified index. Like Swing, bad things can happen if
* the type of the constraints isn't compatible with the layout manager.
*
* @param index 0 based index at which to add the child
* @param child child to be added
* @param constraints constraints the layout manager uses when laying out
* the child
*/
public void addChild(final int index, final PNode child, final Object constraints) {
addChild(index, child, constraints, defaultAnchor);
}
/**
* Adds a child at the specified index.
*
* @param index 0 based index at which to add the child
* @param child child to be added
* @param anchor specifies the location from which layout takes place
*/
public void addChild(final int index, final PNode child, final Anchor anchor) {
addChild(index, child, null, anchor);
}
/**
* Adds a child to the end of the node list.
*
* @param child child to be added
* @param constraints constraints the layout manager uses when laying out
* the child
* @param anchor specifies the location from which layout takes place
*/
public void addChild(final PNode child, final Object constraints, final Anchor anchor) {
// NOTE: since PNode.addChild(PNode) is implemented in terms of
// PNode.addChild(int index), we must do the same.
int index = getChildrenCount();
// workaround a flaw in PNode.addChild(PNode), they should have handled
// this in PNode.addChild(int index).
if (child.getParent() == this) {
index--;
}
addChild(index, child, constraints, anchor);
}
/**
* Adds a child to the end of the node list.
*
* @param child child to be added
*/
public void addChild(final PNode child) {
addChild(child, null, defaultAnchor);
}
/**
* Adds a child to the end of the node list and specifies the given
* constraints.
*
* @param child child to be added
* @param constraints constraints the layout manager uses when laying out
* the child
*/
public void addChild(final PNode child, final Object constraints) {
addChild(child, constraints, defaultAnchor);
}
/**
* Adds a child to the end of the node list.
*
* @param child child to be added
* @param anchor specifies the location from which layout takes place
*/
public void addChild(final PNode child, final Anchor anchor) {
addChild(child, null, anchor);
}
/**
* Adds a collection of nodes to the end of the list.
*
* @param nodes nodes to add to the end of the list
* @param constraints constraints the layout manager uses when laying out
* the child
* @param anchor specifies the location from which layout takes place
*/
public void addChildren(final Collection nodes, final Object constraints, final Anchor anchor) {
final Iterator i = nodes.iterator();
while (i.hasNext()) {
final PNode each = (PNode) i.next();
addChild(each, constraints, anchor);
}
}
/** {@inheritDoc} */
public void addChildren(final Collection nodes) {
addChildren(nodes, null, defaultAnchor);
}
/**
* Adds a collection of nodes to the end of the list.
*
* @param nodes nodes to add to the end of the list
* @param constraints constraints the layout manager uses when laying out
* the child
*/
public void addChildren(final Collection nodes, final Object constraints) {
addChildren(nodes, constraints, defaultAnchor);
}
/**
* Adds a collection of nodes to the end of the list.
*
* @param nodes nodes to add to the end of the list
* @param anchor specifies the location from which layout takes place
*/
public void addChildren(final Collection nodes, final Anchor anchor) {
addChildren(nodes, null, anchor);
}
/**
* Removes a node at a specified index.
*
* @param index 0 based index of the child to be removed
*/
public PNode removeChild(final int index) {
/*
* NOTE: This must be the only super.removeChild call that we make in
* our entire implementation, because all PNode.removeChild methods are
* implemented in terms of this one. Calling other variants of
* super.removeChild will incorrectly invoke our overrides, resulting in
* StackOverflowException.
*/
final PNode node = super.removeChild(index);
removeProxyComponent(node);
return node;
}
/*
* NOTE We don't need to override removeChild(PNode) or removeChildren,
* because they call removeChild(int index). If their implementation ever
* changes, then we'll need to override them.
*/
/**
* PNode.removeAllChildren does not call removeChild, it manipulates an
* internal data structure. So we must override this in a more careful (and
* less efficient) manner.
*/
public void removeAllChildren() {
final Iterator i = getChildrenIterator();
while (i.hasNext()) {
removeChild((PNode) i.next());
}
}
/**
* Adds a proxy component for a node.
*
* @param node node for which to add the proxy component
* @param constraints Constraints to apply when laying out the component
* @param anchor relative anchor point of the underyling proxy component on
* its container
*/
private void addProxyComponent(final PNode node, final Object constraints, final Anchor anchor) {
final ProxyComponent component = new ProxyComponent(node, anchor);
container.add(component, constraints);
node.addPropertyChangeListener(propertyChangeListener);
updateContainerLayout();
}
/**
* Removes a proxy component for a node. Does nothing if the node is not a
* child of the layout.
*
* @param node node from which the proxy container should be removed from.
*/
private void removeProxyComponent(final PNode node) {
if (node != null) {
final ProxyComponent component = getComponentForNode(node);
if (component != null) {
container.remove(component);
node.removePropertyChangeListener(propertyChangeListener);
updateContainerLayout();
}
}
}
/**
* Finds the component that is serving as the proxy for a specific node.
* Returns null if not found.
*/
private ProxyComponent getComponentForNode(final PNode node) {
ProxyComponent nodeComponent = null;
final Component[] components = container.getComponents();
if (components != null) {
for (int i = 0; i < components.length && nodeComponent == null; i++) {
if (components[i] instanceof ProxyComponent) {
final ProxyComponent n = (ProxyComponent) components[i];
if (n.getNode() == node) {
nodeComponent = n;
}
}
}
}
return nodeComponent;
}
/**
* Helper to figure out if the given property name relates to layout.
*
* @param propertyName name of property being tested
*
* @return true property name relates to layout.
*/
private boolean isLayoutProperty(final String propertyName) {
return propertyName.equals(PNode.PROPERTY_VISIBLE) || propertyName.equals(PNode.PROPERTY_FULL_BOUNDS) ||
propertyName.equals(PNode.PROPERTY_BOUNDS) || propertyName.equals(PNode.PROPERTY_TRANSFORM);
}
/**
* Updates the Proxy Container's layout.
*/
private void updateContainerLayout() {
container.invalidate(); // necessary for layouts like BoxLayout that
// would otherwise use stale state
container.setSize(container.getPreferredSize());
container.doLayout();
}
/**
* JComponent that acts as a proxy for a PNode. Provides the PNode's bounds
* info for all bounds-related requests.
*/
private static class ProxyComponent extends JComponent {
private static final long serialVersionUID = 1L;
private final PNode node;
private final Anchor anchor;
public ProxyComponent(final PNode node, final Anchor anchor) {
this.node = node;
this.anchor = anchor;
}
/**
* Returns the associated PNode.
*
* @return associated PNode
*/
public PNode getNode() {
return node;
}
/**
* Report the node's dimensions as the ProxyComponent's preferred size.
*/
public Dimension getPreferredSize() {
// Round up fractional part instead of rounding down; better to
// include the whole node than to chop off part.
final double w = node.getFullBoundsReference().getWidth();
final double h = node.getFullBoundsReference().getHeight();
return new Dimension(roundUp(w), roundUp(h));
}
private int roundUp(final double val) {
return (int) Math.ceil(val);
}
/**
* Return the PNode size as the minimum dimension; required by layouts
* such as BoxLayout.
*
* @return the minimum size for this component
*/
public Dimension getMinimumSize() {
return getPreferredSize();
}
/**
* Sets the bounds of the ProxyComponent and positions the node in the
* area (x,y,w,h) allocated by the layout manager.
*/
public void setBounds(final int x, final int y, final int w, final int h) {
// important to check that the bounds have really changed, or we'll
// cause StackOverflowException
if (x != getX() || y != getY() || w != getWidth() || h != getHeight()) {
super.setBounds(x, y, w, h);
anchor.positionNode(node, x, y, w, h);
}
}
}
/**
* Determines where nodes are anchored in the area allocated by the Swing
* layout manager. Predefined anchor names are similar to GridBagConstraint
* anchors and have the same semantics.
*/
public interface Anchor {
/**
* Positions the node in the bounds defined.
*
* @param node node to be laid out
* @param x left of bounds
* @param y top of bounds
* @param width width of bounds
* @param height height of bounds
*/
void positionNode(PNode node, double x, double y, double width, double height);
/**
* Base class that provides utilities for computing common anchor
* points.
*/
/** Anchors the node's center as the point used when laying it out. */
static final Anchor CENTER = new AbstractAnchor() {
/** {@inheritDoc} */
public void positionNode(final PNode node, final double x, final double y, final double w, final double h) {
node.setOffset(centerX(node, x, w), centerY(node, y, h));
}
};
/** Anchors the node's top center as the point used when laying it out. */
static final Anchor NORTH = new AbstractAnchor() {
/** {@inheritDoc} */
public void positionNode(final PNode node, final double x, final double y, final double w, final double h) {
node.setOffset(centerX(node, x, w), north(node, y, h));
}
};
/** Anchors the node's top right as the point used when laying it out. */
static final Anchor NORTHEAST = new AbstractAnchor() {
/** {@inheritDoc} */
public void positionNode(final PNode node, final double x, final double y, final double w, final double h) {
node.setOffset(east(node, x, w), north(node, y, h));
}
};
/**
* Anchors the node's middle right as the point used when laying it out.
*/
static final Anchor EAST = new AbstractAnchor() {
/** {@inheritDoc} */
public void positionNode(final PNode node, final double x, final double y, final double w, final double h) {
node.setOffset(east(node, x, w), centerY(node, y, h));
}
};
/**
* Anchors the node's bottom right as the point used when laying it out.
*/
static final Anchor SOUTHEAST = new AbstractAnchor() {
/** {@inheritDoc} */
public void positionNode(final PNode node, final double x, final double y, final double w, final double h) {
node.setOffset(east(node, x, w), south(node, y, h));
}
};
/**
* Anchors the node's center bottom as the point used when laying it
* out.
*/
static final Anchor SOUTH = new AbstractAnchor() {
/** {@inheritDoc} */
public void positionNode(final PNode node, final double x, final double y, final double w, final double h) {
node.setOffset(centerX(node, x, w), south(node, y, h));
}
};
/** Anchors the node's bottom left as the point used when laying it out. */
static final Anchor SOUTHWEST = new AbstractAnchor() {
/** {@inheritDoc} */
public void positionNode(final PNode node, final double x, final double y, final double w, final double h) {
node.setOffset(west(node, x, w), south(node, y, h));
}
};
/** Anchors the node's middle left as the point used when laying it out. */
static final Anchor WEST = new AbstractAnchor() {
/** {@inheritDoc} */
public void positionNode(final PNode node, final double x, final double y, final double w, final double h) {
node.setOffset(west(node, x, w), centerY(node, y, h));
}
};
/** Anchors the node's top left as the point used when laying it out. */
static final Anchor NORTHWEST = new AbstractAnchor() {
/** {@inheritDoc} */
public void positionNode(final PNode node, final double x, final double y, final double w, final double h) {
node.setOffset(west(node, x, w), north(node, y, h));
}
};
static abstract class AbstractAnchor implements Anchor {
/**
* Returns the x at which the given node would need to be placed so
* that its center was in the middle of the horizontal segment
* defined by x and width.
*
* @param node node which is being analyzed
* @param x x component of horizontal line segment
* @param width width of horizontal line segment
* @return x at which node would need to be placed so that its
* center matched the center of the line segment
*/
protected static double centerX(final PNode node, final double x, final double width) {
return x + (width - node.getFullBoundsReference().getWidth()) / 2;
}
/**
* Returns the y at which the given node would need to be placed so
* that its center was in the middle of the vertical segment defined
* by y and h.
*
* @param node node which is being analyzed
* @param y y component of horizontal line segment
* @param height height of vertical line segment
* @return y at which node would need to be placed so that its
* center matched the center of the line segment
*/
protected static double centerY(final PNode node, final double y, final double height) {
return y + (height - node.getFullBoundsReference().getHeight()) / 2;
}
/**
* Returns the y at which the given node would need to be placed so
* that its top was against the top of the vertical segment defined.
*
* @param node node which is being analyzed
* @param y y component of horizontal line segment
* @param height height of vertical line segment
* @return y at which node would need to be placed so that its top
* matched the start of the line segment (y)
*/
protected static double north(final PNode node, final double y, final double height) {
return y;
}
/**
* Returns the y at which the given node would need to be placed so
* that its bottom was against the bottom of the vertical range
* defined.
*
* @param node node which is being analyzed
* @param y y component of vertical range
* @param height height of vertical range
* @return y at which node would need to be placed so that its
* bottom matched the bottom of the range
*/
protected static double south(final PNode node, final double y, final double height) {
return y + height - node.getFullBoundsReference().getHeight();
}
/**
* Returns the x at which the given node would need to be placed so
* that its right side was against the right side of the horizontal
* range defined.
*
* @param node node which is being analyzed
* @param x x component of horizontal range
* @param width width of horizontal range
* @return x at which node would need to be placed so that its right
* side touched the right side of the range defined.
*/
protected static double east(final PNode node, final double x, final double width) {
return x + width - node.getFullBoundsReference().getWidth();
}
/**
* Returns the x at which the given node would need to be placed so
* that its left side was against the left side of the horizontal
* range defined.
*
* @param node node which is being analyzed
* @param x x component of horizontal range
* @param width width of horizontal range
* @return x at which node would need to be placed so that its left
* side touched the left side of the range defined (x)
*/
protected static double west(final PNode node, final double x, final double width) {
return x;
}
};
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy