Revision: 908
Author: allain.lalonde
Date: Tue Dec  1 14:06:59 2009
Log: Initial import of code for Issue #150.


http://code.google.com/p/piccolo2d/source/detail?r=908

Added:
   
/piccolo2d.java/trunk/examples/src/main/java/edu/umd/cs/piccolo/examples/SwingLayoutExample.java
   
/piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/swing/SwingLayoutNode.java

=======================================
--- /dev/null
+++  
/piccolo2d.java/trunk/examples/src/main/java/edu/umd/cs/piccolo/examples/SwingLayoutExample.java
         
Tue Dec  1 14:06:59 2009
@@ -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("<html>Five</html>", 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("<html>H<sub>2</sub>O</html>"),  
constraints);
+        constraints.gridy++;
+        gridNode.addChild(new  
PHtmlView("<html>H<sub>3</sub>O<sup>+</sup></html>"), 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);
+    }
+}
=======================================
--- /dev/null
+++  
/piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/swing/SwingLayoutNode.java
        
Tue Dec  1 14:06:59 2009
@@ -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 (cmal...@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: <code>
+     * SwingLayoutNode layoutNode = new SwingLayoutNode();
+     * layoutNode.setLayout( new BoxLayout( layoutNode.getContainer(),  
BoxLayout.Y_AXIS ) );
+     * </code>
+     *
+     * @return container in which children will logically be laid out in
+     */
+    public Container getContainer() {
+        return container;
+    }
+
+    /**
+     * Sets the layout manager.
+     * <p>
+     * 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;
+            }
+        };
+    }
+}

-- 
Piccolo2D Developers Group: http://groups.google.com/group/piccolo2d-dev?hl=en

Reply via email to