diff --git a/examples/src/main/java/edu/umd/cs/piccolo/examples/SwingLayoutExample.java b/examples/src/main/java/edu/umd/cs/piccolo/examples/SwingLayoutExample.java
new file mode 100644
index 0000000..5876634
--- /dev/null
+++ b/examples/src/main/java/edu/umd/cs/piccolo/examples/SwingLayoutExample.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2008-2009, 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 edu.umd.cs.piccolo.examples;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Rectangle2D;
+
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.JTextField;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import edu.umd.cs.piccolo.PCanvas;
+import edu.umd.cs.piccolo.PNode;
+import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
+import edu.umd.cs.piccolo.event.PInputEvent;
+import edu.umd.cs.piccolo.nodes.PHtmlView;
+import edu.umd.cs.piccolo.nodes.PPath;
+import edu.umd.cs.piccolo.nodes.PText;
+import edu.umd.cs.piccolox.pswing.PSwing;
+import edu.umd.cs.piccolox.pswing.PSwingCanvas;
+import edu.umd.cs.piccolox.swing.SwingLayoutNode;
+import edu.umd.cs.piccolox.swing.SwingLayoutNode.Anchor;
+
+public class SwingLayoutExample {
+
+ public static class MyPPath extends PPath {
+ public MyPPath(final Shape shape, final Color color, final Stroke stroke, final Color strokeColor) {
+ super(shape, stroke);
+ setPaint(color);
+ setStrokePaint(strokeColor);
+ }
+ }
+
+ public static void main(final String[] args) {
+
+ final Dimension canvasSize = new Dimension(800, 600);
+ final PCanvas canvas = new PSwingCanvas();
+ canvas.setPreferredSize(canvasSize);
+
+ final PNode rootNode = new PNode();
+ canvas.getLayer().addChild(rootNode);
+ rootNode.addInputEventListener(new PBasicInputEventHandler() {
+ // Shift+Drag up/down will scale the node up/down
+ public void mouseDragged(final PInputEvent event) {
+ super.mouseDragged(event);
+ if (event.isShiftDown()) {
+ event.getPickedNode().scale(event.getCanvasDelta().height > 0 ? 0.98 : 1.02);
+ }
+ }
+ });
+
+ final BorderLayout borderLayout = new BorderLayout();
+ borderLayout.setHgap(10);
+ borderLayout.setVgap(5);
+ final SwingLayoutNode borderLayoutNode = new SwingLayoutNode(borderLayout);
+ borderLayoutNode.addChild(new PText("North"), BorderLayout.NORTH);
+ borderLayoutNode.setAnchor(Anchor.CENTER);
+ borderLayoutNode.addChild(new PText("South"), BorderLayout.SOUTH);
+ borderLayoutNode.setAnchor(Anchor.WEST);
+ borderLayoutNode.addChild(new PText("East"), BorderLayout.EAST);
+ borderLayoutNode.addChild(new PText("West"), BorderLayout.WEST);
+ borderLayoutNode.addChild(new PText("CENTER"), BorderLayout.CENTER);
+ borderLayoutNode.setOffset(100, 100);
+ rootNode.addChild(borderLayoutNode);
+
+ final SwingLayoutNode flowLayoutNode = new SwingLayoutNode(new FlowLayout());
+ flowLayoutNode.addChild(new PText("1+1"));
+ flowLayoutNode.addChild(new PText("2+2"));
+ flowLayoutNode.setOffset(200, 200);
+ rootNode.addChild(flowLayoutNode);
+
+ final SwingLayoutNode gridBagLayoutNode = new SwingLayoutNode(new GridBagLayout());
+ final GridBagConstraints gridBagConstraints = new GridBagConstraints();
+ gridBagConstraints.gridx = GridBagConstraints.RELATIVE;
+ gridBagLayoutNode.addChild(new PText("FirstNode"), gridBagConstraints);
+ gridBagLayoutNode.addChild(new PText("SecondNode"), gridBagConstraints);
+ gridBagConstraints.insets = new Insets(50, 50, 50, 50);
+ gridBagLayoutNode.addChild(new PText("ThirdNode"), gridBagConstraints);
+ gridBagLayoutNode.setOffset(400, 250);
+ rootNode.addChild(gridBagLayoutNode);
+
+ final SwingLayoutNode boxLayoutNode = new SwingLayoutNode();
+ boxLayoutNode.setLayout(new BoxLayout(boxLayoutNode.getContainer(), BoxLayout.Y_AXIS));
+ boxLayoutNode.addChild(new MyPPath(new Rectangle2D.Double(0, 0, 50, 50), Color.yellow, new BasicStroke(2),
+ Color.red));
+ boxLayoutNode.addChild(new MyPPath(new Rectangle2D.Double(0, 0, 100, 50), Color.orange, new BasicStroke(2),
+ Color.blue));
+ final SwingLayoutNode innerNode = new SwingLayoutNode(); // nested
+ // layout
+ innerNode.addChild(new PSwing(new JLabel("foo")));
+ innerNode.addChild(new PSwing(new JLabel("bar")));
+ boxLayoutNode.addChild(innerNode, Anchor.CENTER);
+ boxLayoutNode.setOffset(300, 300);
+ rootNode.addChild(boxLayoutNode);
+
+ final SwingLayoutNode horizontalLayoutNode = new SwingLayoutNode(new GridBagLayout());
+ horizontalLayoutNode.addChild(new PSwing(new JButton("Zero")));
+ horizontalLayoutNode.addChild(new PSwing(new JButton("One")));
+ horizontalLayoutNode.addChild(new PSwing(new JButton("Two")));
+ horizontalLayoutNode.addChild(new PSwing(new JLabel("Three")));
+ horizontalLayoutNode.addChild(new PSwing(new JSlider()));
+ horizontalLayoutNode.addChild(new PSwing(new JTextField("Four")));
+ final PHtmlView htmlNode = new PHtmlView("Five", new JLabel().getFont().deriveFont(15f),
+ Color.blue);
+ htmlNode.scale(3);
+ horizontalLayoutNode.addChild(htmlNode);
+ horizontalLayoutNode.setOffset(100, 450);
+ rootNode.addChild(horizontalLayoutNode);
+
+ // 3x2 grid of values, shapes and labels (similar to a layout in
+ // acid-base-solutions)
+ final SwingLayoutNode gridNode = new SwingLayoutNode(new GridBagLayout());
+ final GridBagConstraints constraints = new GridBagConstraints();
+ constraints.insets = new Insets(10, 10, 10, 10);
+ /*---- column of values, right justified ---*/
+ constraints.gridy = 0; // row
+ constraints.gridx = 0; // column
+ constraints.anchor = GridBagConstraints.EAST;
+ final PText dynamicNode = new PText("0"); // will be controlled by
+ // dynamicSlider
+ gridNode.addChild(dynamicNode, constraints);
+ constraints.gridy++;
+ gridNode.addChild(new PText("0"), constraints);
+ /*---- column of shapes, center justified ---*/
+ constraints.gridy = 0; // row
+ constraints.gridx++; // column
+ constraints.anchor = GridBagConstraints.CENTER;
+ final PPath redCircle = new PPath(new Ellipse2D.Double(0, 0, 25, 25));
+ redCircle.setPaint(Color.RED);
+ gridNode.addChild(redCircle, constraints);
+ constraints.gridy++;
+ final PPath greenCircle = new PPath(new Ellipse2D.Double(0, 0, 25, 25));
+ greenCircle.setPaint(Color.GREEN);
+ gridNode.addChild(greenCircle, constraints);
+ /*---- column of labels, left justified ---*/
+ constraints.gridy = 0; // row
+ constraints.gridx++; // column
+ constraints.anchor = GridBagConstraints.WEST;
+ gridNode.addChild(new PHtmlView("H2O"), constraints);
+ constraints.gridy++;
+ gridNode.addChild(new PHtmlView("H3O+"), constraints);
+ gridNode.scale(2.0);
+ gridNode.setOffset(400, 50);
+ rootNode.addChild(gridNode);
+
+ final JPanel controlPanel = new JPanel();
+ controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));
+ final JSlider dynamicSlider = new JSlider(0, 1000, 0); // controls
+ // dynamicNode
+ dynamicSlider.setMajorTickSpacing(dynamicSlider.getMaximum());
+ dynamicSlider.setPaintTicks(true);
+ dynamicSlider.setPaintLabels(true);
+ dynamicSlider.addChangeListener(new ChangeListener() {
+
+ public void stateChanged(final ChangeEvent e) {
+ dynamicNode.setText(String.valueOf(dynamicSlider.getValue()));
+ }
+ });
+ controlPanel.add(dynamicSlider);
+
+ final JPanel appPanel = new JPanel(new BorderLayout());
+ appPanel.add(canvas, BorderLayout.CENTER);
+ appPanel.add(controlPanel, BorderLayout.EAST);
+
+ final JFrame frame = new JFrame();
+ frame.setContentPane(appPanel);
+ frame.pack();
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.setVisible(true);
+ }
+}
diff --git a/extras/src/main/java/edu/umd/cs/piccolox/swing/SwingLayoutNode.java b/extras/src/main/java/edu/umd/cs/piccolox/swing/SwingLayoutNode.java
new file mode 100644
index 0000000..5369b31
--- /dev/null
+++ b/extras/src/main/java/edu/umd/cs/piccolox/swing/SwingLayoutNode.java
@@ -0,0 +1,657 @@
+/*
+ * Copyright (c) 2008-2009, 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 edu.umd.cs.piccolox.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 edu.umd.cs.piccolo.PNode;
+
+/**
+ * Uses Swing layout managers to position PNodes.
+ *
+ * @author Sam Reid
+ * @author Chris Malley (cmalley@pixelzoom.com)
+ */
+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 JPanel container;
+
+ private final PropertyChangeListener propertyChangeListener;
+ private Anchor anchor;
+
+ /**
+ * 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) {
+ container = new JPanel(layoutManager);
+ propertyChangeListener = new PropertyChangeListener() {
+ public void propertyChange(final PropertyChangeEvent event) {
+ final String propertyName = event.getPropertyName();
+ if (isLayoutProperty(propertyName)) {
+ updateContainerLayout();
+ }
+ }
+ };
+ anchor = 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
+ */
+ public void setAnchor(final Anchor anchor) {
+ this.anchor = anchor;
+ }
+
+ /**
+ * Returns the anchor being used by this LayoutManager.
+ *
+ * @return anchor currently being used when laying out children.
+ */
+ public Anchor getAnchor() {
+ return anchor;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Sets the layout manager.
+ *
+ * It's recommended that you avoid using this method, it's a can of worms. + * Like Swing, if you call this after adding nodes, the results may be + * unpredictable. You'll also have problems if the constraints that you + * specified to addChild aren't compatible with the new layout manager, or + * if the new layout manager requires constraints. + * + * @param layoutManager + */ + public void setLayout(final LayoutManager layoutManager) { + container.setLayout(layoutManager); + updateContainerLayout(); + } + + /** + * 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 anchor specifies the location from which layout takes place + */ + public void addChild(final int index, final PNode child, final Object constraints, final Anchor anchor) { + /* + * 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, anchor); + } + + /** {@inheritDoc} */ + public void addChild(final int index, final PNode child) { + addChild(index, child, null, anchor); + } + + /** + * 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, anchor); + } + + /** + * 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, anchor); + } + + /** + * 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, anchor); + } + + /** + * 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, 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 + */ + public void addChildren(final Collection nodes, final Object constraints) { + addChildren(nodes, constraints, anchor); + } + + /** + * 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. */ + public 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. */ + public 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. */ + public 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. + */ + public 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. + */ + public 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. + */ + public 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. */ + public 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. */ + public 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. */ + public 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)); + } + }; + + public 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 w) { + return x + w - 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 w) { + return x; + } + }; + } +} \ No newline at end of file