Revision: 863
Author: allain.lalonde
Date: Thu Oct 29 14:06:31 2009
Log: Applying PSwing file provided by sreid. Javadocs have been re-added.
And some Refactoring has taken place.
http://code.google.com/p/piccolo2d/source/detail?r=863
Modified:
/piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwing.java
/piccolo2d.java/trunk/extras/src/test/java/edu/umd/cs/piccolox/pswing/PSwingTest.java
=======================================
---
/piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwing.java
Sat Oct 24 08:13:41 2009
+++
/piccolo2d.java/trunk/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwing.java
Thu Oct 29 14:06:31 2009
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2009, Piccolo2D project, http://piccolo2d.org
+ * Copyright (c) 2008, Piccolo2D project, http://piccolo2d.org
* Copyright (c) 1998-2008, University of Maryland
* All rights reserved.
*
@@ -39,11 +39,7 @@
import java.awt.Stroke;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
-import java.awt.event.ContainerAdapter;
-import java.awt.event.ContainerEvent;
-import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
@@ -51,7 +47,6 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import javax.swing.JComponent;
import javax.swing.RepaintManager;
@@ -140,9 +135,9 @@
JPopupMenu. In order to implement ToolTips properly, we would need to have
a method in ToolTipManager that allows us to set the current manager, as
is possible with RepaintManager. In order to implement JPopupMenu, we
- will likely need to reimplement JPopupMenu to function in Piccolo with
+ will likely need to re-implement JPopupMenu to function in Piccolo2d with
a transformed Graphics and to insert itself in the proper place in the
- Piccolo scenegraph.
+ Piccolo2d scenegraph.
*/
@@ -158,7 +153,6 @@
* canvas.getLayer().addChild(swing);
* </pre>
*
- * </p>
* <p>
* NOTE: PSwing has the current limitation that it does not listen for
Container
* events. This is only an issue if you create a PSwing and later add Swing
@@ -187,18 +181,18 @@
* </p>
* <p>
* <b>Warning:</b> Serialized objects of this class will not be compatible
with
- * future Piccolo2D releases. The current serialization support is
appropriate
- * for short term storage or RMI between applications running the same
version
- * of Piccolo2D. A future release of Piccolo2D will provide support for
long
- * term persistence.
+ * future Piccolo releases. The current serialization support is
appropriate for
+ * short term storage or RMI between applications running the same version
of
+ * Piccolo. A future release of Piccolo will provide support for long term
+ * persistence.
* </p>
*
* @author Sam R. Reid
* @author Benjamin B. Bederson
* @author Lance E. Good
+ *
*/
public class PSwing extends PNode implements Serializable,
PropertyChangeListener {
-
/** Default serial version UID. */
private static final long serialVersionUID = 1L;
@@ -206,26 +200,25 @@
public static final String PSWING_PROPERTY = "PSwing";
/** Temporary repaint bounds. */
- private static PBounds TEMP_REPAINT_BOUNDS2 = new PBounds();
-
- /** Default greek threshold, <code>0.3d</code>. */
+ private static final PBounds TEMP_REPAINT_BOUNDS2 = new PBounds();
+
+ /** Default Greek threshold, <code>0.3d</code>. */
private static final double DEFAULT_GREEK_THRESHOLD = 0.3d;
+ /** The cutoff at which the Swing component is rendered greek. */
+ private double greekThreshold = DEFAULT_GREEK_THRESHOLD;
+
/** Swing component for this Swing node. */
private JComponent component = null;
/** Minimum font size. */
private double minFontSize = Double.MAX_VALUE;
- private static final AffineTransform IDENTITY_TRANSFORM = new
AffineTransform();
-
/**
* Default stroke, <code>new BasicStroke()</code>. Cannot be made
static
* because BasicStroke is not serializable.
*/
- private transient Stroke defaultStroke = new BasicStroke();
-
- private static final Color BUFFER_BACKGROUND_COLOR = new Color(0, 0,
0, 0);
+ private Stroke defaultStroke = new BasicStroke();
/**
* Default font, 12 point <code>"SansSerif"</code>. Will be made final
in
@@ -233,24 +226,19 @@
*/
// public static final Font DEFAULT_FONT = new Font(Font.SANS_SERIF,
// Font.PLAIN, 12); jdk 1.6+
- public static final Font DEFAULT_FONT = new Font("SansSerif",
Font.PLAIN, 12);
-
- /** Greek threshold in scale. */
- private double greekThreshold = DEFAULT_GREEK_THRESHOLD;
+ private static final Font DEFAULT_FONT = new Font("Serif", Font.PLAIN,
12);
/** Swing canvas for this swing node. */
private PSwingCanvas canvas;
- private BufferedImage buffer;
-
/**
* Used to keep track of which nodes we've attached listeners to since
no
* built in support in PNode.
*/
- private final List listeningTo = new ArrayList();
-
- /* The parent listener for camera/canvas changes. */
- private final transient PropertyChangeListener parentListener = new
PropertyChangeListener() {
+ private final ArrayList listeningTo = new ArrayList();
+
+ /** The parent listener for camera/canvas changes. */
+ private final PropertyChangeListener parentListener = new
PropertyChangeListener() {
/** {...@inheritdoc} */
public void propertyChange(final PropertyChangeEvent evt) {
final PNode parent = (PNode) evt.getNewValue();
@@ -261,7 +249,6 @@
else {
listenForCanvas(parent);
}
-
}
/**
@@ -271,7 +258,7 @@
* @param fromParent Parent to start with for clearing listeners
*/
private void clearListeners(final PNode fromParent) {
- if (fromParent != null && listeningTo(fromParent)) {
+ if (fromParent != null && isListeningTo(fromParent)) {
fromParent.removePropertyChangeListener(PNode.PROPERTY_PARENT,
parentListener);
listeningTo.remove(fromParent);
clearListeners(fromParent.getParent());
@@ -280,6 +267,12 @@
};
+ private final PropertyChangeListener reshapeListener = new
PropertyChangeListener() {
+ public void propertyChange(final PropertyChangeEvent evt) {
+ repaint();
+ }
+ };
+
/**
* Create a new visual component wrapper for the specified Swing
component.
*
@@ -288,9 +281,9 @@
public PSwing(final JComponent component) {
this.component = component;
component.putClientProperty(PSWING_PROPERTY, this);
- init(component);
- component.revalidate();
-
+ initializeComponent(component);
+
+ component.revalidate();
component.addPropertyChangeListener(new PropertyChangeListener() {
/** {...@inheritdoc} */
public void propertyChange(final PropertyChangeEvent evt) {
@@ -329,57 +322,55 @@
* bounds of this PNode.
*/
void reshape() {
- component.setBounds(0, 0, component.getPreferredSize().width,
component.getPreferredSize().height);
+ // Avoid setBounds if it is unnecessary
+ // TODO: should we make sure this is called at least once
+ // TODO: does this sometimes need to be called when size already
equals
+ // preferred size, to relayout/update things?
+ if (componentNeedsResizing()) {
+ component.setBounds(0, 0, component.getPreferredSize().width,
component.getPreferredSize().height);
+ }
setBounds(0, 0, component.getPreferredSize().width,
component.getPreferredSize().height);
}
- /** {...@inheritdoc} */
- protected void paint(final PPaintContext paintContext) {
- final Graphics2D graphics = paintContext.getGraphics();
+ private boolean componentNeedsResizing() {
+ return component.getWidth() != component.getPreferredSize().width
+ || component.getHeight() !=
component.getPreferredSize().height;
+ }
+
+ /**
+ * Determines if the Swing component should be rendered normally or as
a
+ * filled rectangle.
+ * <p/>
+ * The transform, clip, and composite will be set appropriately when
this
+ * object is rendered. It is up to this object to restore the
transform,
+ * clip, and composite of the Graphics2D if this node changes any of
them.
+ * However, the color, font, and stroke are unspecified by Piccolo.
This
+ * object should set those things if they are used, but they do not
need to
+ * be restored.
+ *
+ * @param renderContext Contains information about current render.
+ */
+ public void paint(final PPaintContext renderContext) {
+ final Graphics2D g2 = renderContext.getGraphics();
if (defaultStroke == null) {
defaultStroke = new BasicStroke();
}
- graphics.setStroke(defaultStroke);
-
- graphics.setFont(DEFAULT_FONT);
+
+ g2.setStroke(defaultStroke);
+ g2.setFont(DEFAULT_FONT);
if (component.getParent() == null) {
component.revalidate();
}
- if (shouldRenderGreek(paintContext)) {
- paintGreek(graphics);
+ if (shouldRenderGreek(renderContext)) {
+ paintAsGreek(g2);
}
else {
- paintComponent(graphics);
+ paint(g2);
}
}
-
- /**
- * Return the greek threshold in scale. When the scale will be below
this
- * threshold the Swing component is rendered as 'greek' instead of
painting
- * the Swing component. Defaults to {...@link #DEFAULT_GREEK_THRESHOLD}.
- *
- * @see PSwing#paintGreek(PPaintContext)
- * @return the current greek threshold in scale
- */
- public double getGreekThreshold() {
- return greekThreshold;
- }
-
- /**
- * Set the greek threshold in scale to <code>greekThreshold</code>.
When the
- * scale will be below this threshold the Swing component is rendered
as
- * 'greek' instead of painting the Swing component..
- *
- * @see PSwing#paintGreek(PPaintContext)
- * @param greekThreshold greek threshold in scale
- */
- public void setGreekThreshold(final double greekThreshold) {
- this.greekThreshold = greekThreshold;
- invalidatePaint();
- }
/**
* Return true if this Swing node should render as greek given the
specified
@@ -394,27 +385,30 @@
}
/**
- * Paint the Swing component as greek with the specified paint
context. The
- * implementation in this class paints a rectangle with the Swing
- * component's background color and paints a stroke with the Swing
- * component's foreground color.
+ * Paints the Swing component as greek.
*
- * @param paintContext paint context
+ * @param g2 The graphics used to render the filled rectangle
*/
- protected void paintGreek(final Graphics2D graphics) {
+ public void paintAsGreek(final Graphics2D g2) {
final Color background = component.getBackground();
final Color foreground = component.getForeground();
final Rectangle2D rect = getBounds();
if (background != null) {
- graphics.setColor(background);
- }
- graphics.fill(rect);
+ g2.setColor(background);
+ }
+ g2.fill(rect);
if (foreground != null) {
- graphics.setColor(foreground);
- }
- graphics.draw(rect);
+ g2.setColor(foreground);
+ }
+ g2.draw(rect);
+ }
+
+ /** {...@inheritdoc} */
+ public void setVisible(final boolean visible) {
+ super.setVisible(visible);
+ component.setVisible(visible);
}
/**
@@ -422,82 +416,44 @@
* associated with this PSwing.
*/
public void removeFromSwingWrapper() {
- if (canvas != null &&
Arrays.asList(canvas.getSwingWrapper().getComponents()).contains(component))
{
+ if (canvas != null && isComponentSwingWrapped()) {
canvas.getSwingWrapper().remove(component);
}
}
+
+ private boolean isComponentSwingWrapped() {
+ return
Arrays.asList(canvas.getSwingWrapper().getComponents()).contains(component);
+ }
/**
- * Paint the Swing component with the specified paint context.
+ * Renders the wrapped component to the graphics context provided.
*
- * @param paintContext paint context
+ * @param g2 graphics context for rendering the JComponent
*/
- protected void paintComponent(final Graphics2D g2) {
+ public void paint(final Graphics2D g2) {
if (component.getBounds().isEmpty()) {
// The component has not been initialized yet.
return;
}
- PSwingRepaintManager manager = (PSwingRepaintManager)
RepaintManager.currentManager(component);
+ final PSwingRepaintManager manager = (PSwingRepaintManager)
RepaintManager.currentManager(component);
manager.lockRepaint(component);
- Graphics2D bufferedGraphics = null;
- if (!isBufferValid()) {
- // Get the graphics context associated with a new buffered
image.
- // Use TYPE_INT_ARGB_PRE so that transparent components look
good on
- // Windows.
- buffer = new BufferedImage(component.getWidth(),
component.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE);
- bufferedGraphics = buffer.createGraphics();
- }
- else {
- // Use the graphics context associated with the existing
buffered
- // image
- bufferedGraphics = buffer.createGraphics();
- // Clear the buffered image to prevent artifacts on Macintosh
- bufferedGraphics.setBackground(BUFFER_BACKGROUND_COLOR);
- bufferedGraphics.clearRect(0, 0, component.getWidth(),
component.getHeight());
- }
-
- // Start with the rendering hints from the provided graphics
context
- bufferedGraphics.setRenderingHints(g2.getRenderingHints());
-
- // PSwing sometimes causes JComponent text to render with "..."
when
- // fractional font metrics are enabled. These are now always
disabled
- // for the offscreen buffer.
-
bufferedGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
- RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
-
- // Draw the component to the buffer
- component.paint(bufferedGraphics);
-
- // Draw the buffer to g2's associated drawing surface
- g2.drawRenderedImage(buffer, IDENTITY_TRANSFORM);
+ final RenderingHints oldHints = g2.getRenderingHints();
+
+ g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
+ component.paint(g2);
+
+ g2.setRenderingHints(oldHints);
manager.unlockRepaint(component);
}
-
- /**
- * Tells whether the buffer for the image of the Swing components is
- * currently valid.
- *
- * @return true if the buffer is currently valid
- */
- private boolean isBufferValid() {
- return !(buffer == null || buffer.getWidth() !=
component.getWidth() || buffer.getHeight() != component
- .getHeight());
- }
-
- /** {...@inheritdoc} */
- public void setVisible(final boolean visible) {
- super.setVisible(visible);
- component.setVisible(visible);
- }
/**
* Repaints the specified portion of this visual component. Note that
the
* input parameter may be modified as a result of this call.
*
- * @param repaintBounds bounds needing repainting
+ * @param repaintBounds bounds that need repainting
*/
public void repaint(final PBounds repaintBounds) {
final Shape sh =
getTransform().createTransformedShape(repaintBounds);
@@ -506,21 +462,39 @@
}
/**
- * Sets the Swing component's bounds to its preferred bounds unless it
- * already is set to its preferred size. Also updates the visual
components
- * copy of these bounds
+ * Returns the Swing component that this visual component wraps.
+ *
+ * @return The Swing component wrapped by this PSwing node
*/
- public void computeBounds() {
- reshape();
+ public JComponent getComponent() {
+ return component;
}
+ // TODO: make this private and internal by listenening for
componentAdded on
+ // known components
+ // This disables double buffering on any new components; if you add
+ // components to the target jcomponent without disabling double
buffering,
+ // then there may be many graphical artifacts.
/**
- * Return the Swing component that this Swing node wraps.
+ * Disables double buffering on the wrapped component and all of its
+ * children.
*
- * @return the Swing component that this Swing node wraps
+ * I'm assuming that the intent of the is method is that it should be
called
+ * explicitly by anyone making changes to the hierarchy of the Swing
+ * component graph.
*/
- public JComponent getComponent() {
- return component;
+ private void componentHierarchyChanged() {
+ disableDoubleBuffering(component);
+ }
+
+ private void disableDoubleBuffering(final JComponent targetComponent) {
+ targetComponent.setDoubleBuffered(false);
+ for (int i = 0; i < targetComponent.getComponentCount(); i++) {
+ final Component c = targetComponent.getComponent(i);
+ if (c instanceof JComponent) {
+ disableDoubleBuffering((JComponent) c);
+ }
+ }
}
/**
@@ -535,60 +509,59 @@
*
* @param c The Component to be recursively unDoubleBuffered
*/
- void init(final Component c) {
+ private void initializeComponent(final Component c) {
+
if (c.getFont() != null) {
minFontSize = Math.min(minFontSize, c.getFont().getSize());
}
-
- if (c instanceof Container) {
- final Component[] children = ((Container) c).getComponents();
- if (children != null) {
- for (int j = 0; j < children.length; j++) {
- init(children[j]);
- }
- }
- ((Container) c).addContainerListener(new ContainerAdapter() {
- /** {...@inheritdoc} */
- public void componentAdded(final ContainerEvent event) {
- init(event.getChild());
- }
- });
- }
+ c.addPropertyChangeListener("font", this);
+
+ // Update shape when any property (such as text or font) changes.
+ c.addPropertyChangeListener(reshapeListener);
+
+ c.addComponentListener(new ComponentAdapter() {
+ public void componentResized(final ComponentEvent e) {
+ reshape();
+ }
+
+ public void componentShown(final ComponentEvent e) {
+ reshape();
+ }
+ });
+
+ if (c instanceof Container) {
+ initializeChildren((Container) c);
+ }
+
if (c instanceof JComponent) {
((JComponent) c).setDoubleBuffered(false);
- c.addPropertyChangeListener("font", this);
- c.addComponentListener(new ComponentAdapter() {
- public void componentResized(final ComponentEvent e) {
- computeBounds();
- }
-
- public void componentShown(final ComponentEvent e) {
- computeBounds();
- }
- });
}
}
- /** {...@inheritdoc} */
- public void propertyChange(final PropertyChangeEvent evt) {
- if (component.isAncestorOf((Component) evt.getSource()) &&
((Component) evt.getSource()).getFont() != null) {
- minFontSize = Math.min(minFontSize, ((Component)
evt.getSource()).getFont().getSize());
+ private void initializeChildren(final Container c) {
+ final Component[] children = c.getComponents();
+ if (children != null) {
+ for (int j = 0; j < children.length; j++) {
+ initializeComponent(children[j]);
+ }
}
}
/**
- * Read this node and all of its descendants in from the given input
stream.
+ * Listens for changes in font on components rooted at this PSwing.
*
- * @param in the stream to read from
- *
- * @throws IOException when an error occurs speaking to underlying
- * ObjectOutputStream
- * @throws ClassNotFoundException when a class is deserialized that no
- * longer exists. This can happen if it's renamed or
deleted.
+ * @param evt property change event representing the change in font
*/
+ public void propertyChange(final PropertyChangeEvent evt) {
+ final Component source = (Component) evt.getSource();
+ if (source.getFont() != null && component.isAncestorOf(source)) {
+ minFontSize = Math.min(minFontSize,
source.getFont().getSize());
+ }
+ }
+
private void readObject(final ObjectInputStream in) throws
IOException, ClassNotFoundException {
in.defaultReadObject();
- init(component);
+ initializeComponent(component);
}
/**
@@ -607,14 +580,11 @@
listenToNode(p);
final PNode parent = p;
- if (parent instanceof PCamera) {
- final PCamera cam = (PCamera) parent;
- if (cam.getComponent() instanceof PSwingCanvas) {
- updateCanvas((PSwingCanvas) cam.getComponent());
- }
- }
- else if (parent instanceof PLayer) {
+ // System.out.println( "parent = " + parent.getClass() );
+ if (parent instanceof PLayer) {
final PLayer player = (PLayer) parent;
+ // System.out.println( "Found player: with " +
+ // player.getCameraCount() + " cameras" );
for (int i = 0; i < player.getCameraCount(); i++) {
final PCamera cam = player.getCamera(i);
if (cam.getComponent() instanceof PSwingCanvas) {
@@ -628,13 +598,13 @@
}
/**
- * Attach a listener to the specified node, if one has not already been
- * attached.
+ * Attach a property change listener to the specified node, if one has
not
+ * already been attached.
*
* @param node the node to listen to for parent/pcamera/pcanvas changes
*/
private void listenToNode(final PNode node) {
- if (!listeningTo(node)) {
+ if (!isListeningTo(node)) {
listeningTo.add(node);
node.addPropertyChangeListener(PNode.PROPERTY_PARENT,
parentListener);
}
@@ -648,7 +618,7 @@
* @return true if this PSwing is already listening to the specified
node
* for camera/canvas changes
*/
- private boolean listeningTo(final PNode node) {
+ private boolean isListeningTo(final PNode node) {
for (int i = 0; i < listeningTo.size(); i++) {
final PNode pNode = (PNode) listeningTo.get(i);
if (pNode == node) {
@@ -665,16 +635,48 @@
* @param newCanvas the new PSwingCanvas (may be null)
*/
private void updateCanvas(final PSwingCanvas newCanvas) {
- if (newCanvas != canvas) {
- if (canvas != null) {
- canvas.removePSwing(this);
- }
+ if (newCanvas == canvas) {
+ return;
+ }
+
+ if (canvas != null) {
+ canvas.removePSwing(this);
+ }
+
+ if (newCanvas != null) {
canvas = newCanvas;
- if (newCanvas != null) {
- canvas.addPSwing(this);
- reshape();
- repaint();
- }
- }
+ canvas.addPSwing(this);
+ reshape();
+ repaint();
+ canvas.invalidate();
+ canvas.revalidate();
+ canvas.repaint();
+ }
+
+ }
+
+ /**
+ * Return the Greek threshold scale. When the scale will be below this
+ * threshold the Swing component is rendered as 'Greek' instead of
painting
+ * the Swing component. Defaults to {...@link #DEFAULT_GREEK_THRESHOLD}.
+ *
+ * @see PSwing#paintGreek(PPaintContext)
+ * @return the current Greek threshold scale
+ */
+ public double getGreekThreshold() {
+ return greekThreshold;
+ }
+
+ /**
+ * Set the Greek threshold in scale to <code>greekThreshold</code>.
When the
+ * scale will be below this threshold the Swing component is rendered
as
+ * 'Greek' instead of painting the Swing component..
+ *
+ * @see PSwing#paintGreek(PPaintContext)
+ * @param greekThreshold Greek threshold in scale
+ */
+ public void setGreekThreshold(final double greekThreshold) {
+ this.greekThreshold = greekThreshold;
+ invalidatePaint();
}
}
=======================================
---
/piccolo2d.java/trunk/extras/src/test/java/edu/umd/cs/piccolox/pswing/PSwingTest.java
Fri Oct 23 13:53:33 2009
+++
/piccolo2d.java/trunk/extras/src/test/java/edu/umd/cs/piccolox/pswing/PSwingTest.java
Thu Oct 29 14:06:31 2009
@@ -252,6 +252,7 @@
PSwingCanvas canvas1 = new PSwingCanvas();
PSwing label = new PSwing(new JLabel("Hello"));
canvas1.getLayer().addChild(label);
+ assertEquals(1, canvas1.getSwingWrapper().getComponentCount());
label.removeFromParent();
assertEquals(0, canvas1.getSwingWrapper().getComponentCount());
}
@@ -272,7 +273,7 @@
public MockPaintingPSwing(JComponent component) {
super(component);
- }
+ }
public void paintOnto(BufferedImage image) {
PPaintContext paintContext = new
PPaintContext(image.createGraphics());
@@ -292,16 +293,16 @@
}
public void paintComponentOnto(BufferedImage image) {
- paintComponent(image.createGraphics());
+ paint(image.createGraphics());
}
- protected void paintComponent(Graphics2D paintContext) {
- super.paintComponent(paintContext);
+ public void paint(Graphics2D paintContext) {
+ super.paint(paintContext);
paintedComponent = true;
}
- protected void paintGreek(Graphics2D paintContext) {
- super.paintGreek(paintContext);
+ public void paintAsGreek(Graphics2D paintContext) {
+ super.paintAsGreek(paintContext);
paintedGreek = true;
}
--~--~---------~--~----~------------~-------~--~----~
Piccolo2D Developers Group: http://groups.google.com/group/piccolo2d-dev?hl=en
-~----------~----~----~----~------~----~------~--~---