diff --git a/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwing.java b/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwing.java index 6caa61c..82c27df 100644 --- a/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwing.java +++ b/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwing.java @@ -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); * * - *
** 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 @@ *
** Warning: 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. *
* * @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(); + private static final PBounds TEMP_REPAINT_BOUNDS2 = new PBounds(); - /** Default greek threshold,0.3d
. */
+ /** Default Greek threshold, 0.3d
. */
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, new BasicStroke()
. 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 "SansSerif"
. 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();
+ private final ArrayList listeningTo = new ArrayList();
- /* The parent listener for camera/canvas changes. */
- private final transient PropertyChangeListener parentListener = new PropertyChangeListener() {
+ /** 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,59 +322,57 @@
* 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.
+ *
+ * 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 greekThreshold
. 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
* paint context.
*
@@ -394,97 +385,24 @@
}
/**
- * 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);
+ g2.setColor(background);
}
- graphics.fill(rect);
+ g2.fill(rect);
if (foreground != null) {
- graphics.setColor(foreground);
+ g2.setColor(foreground);
}
- graphics.draw(rect);
- }
-
- /**
- * Remove from the SwingWrapper; throws an exception if no canvas is
- * associated with this PSwing.
- */
- public void removeFromSwingWrapper() {
- if (canvas != null && Arrays.asList(canvas.getSwingWrapper().getComponents()).contains(component)) {
- canvas.getSwingWrapper().remove(component);
- }
- }
-
- /**
- * Paint the Swing component with the specified paint context.
- *
- * @param paintContext paint context
- */
- protected void paintComponent(final Graphics2D g2) {
- if (component.getBounds().isEmpty()) {
- // The component has not been initialized yet.
- return;
- }
-
- 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);
-
- 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());
+ g2.draw(rect);
}
/** {@inheritDoc} */
@@ -494,10 +412,48 @@
}
/**
+ * Remove from the SwingWrapper; throws an exception if no canvas is
+ * associated with this PSwing.
+ */
+ public void removeFromSwingWrapper() {
+ if (canvas != null && isComponentSwingWrapped()) {
+ canvas.getSwingWrapper().remove(component);
+ }
+ }
+
+ private boolean isComponentSwingWrapped() {
+ return Arrays.asList(canvas.getSwingWrapper().getComponents()).contains(component);
+ }
+
+ /**
+ * Renders the wrapped component to the graphics context provided.
+ *
+ * @param g2 graphics context for rendering the JComponent
+ */
+ public void paint(final Graphics2D g2) {
+ if (component.getBounds().isEmpty()) {
+ // The component has not been initialized yet.
+ return;
+ }
+
+ final PSwingRepaintManager manager = (PSwingRepaintManager) RepaintManager.currentManager(component);
+ manager.lockRepaint(component);
+
+ final RenderingHints oldHints = g2.getRenderingHints();
+
+ g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
+ component.paint(g2);
+
+ g2.setRenderingHints(oldHints);
+
+ manager.unlockRepaint(component);
+ }
+
+ /**
* 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,23 +462,41 @@
}
/**
- * 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
- */
- public void computeBounds() {
- reshape();
- }
-
- /**
- * Return the Swing component that this Swing node wraps.
+ * Returns the Swing component that this visual component wraps.
*
- * @return the Swing component that this Swing node wraps
+ * @return The Swing component wrapped by this PSwing node
*/
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.
+ /**
+ * Disables double buffering on the wrapped component and all of its
+ * children.
+ *
+ * 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.
+ */
+ 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);
+ }
+ }
+ }
+
/**
* We need to turn off double buffering of Swing components within Piccolo
* since all components contained within a native container use the same
@@ -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());
}
+ 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) {
- 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());
- }
- });
+ 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);
- }
- canvas = newCanvas;
- if (newCanvas != null) {
- canvas.addPSwing(this);
- reshape();
- repaint();
- }
+ if (newCanvas == canvas) {
+ return;
}
+
+ if (canvas != null) {
+ canvas.removePSwing(this);
+ }
+
+ if (newCanvas != null) {
+ canvas = newCanvas;
+ 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 greekThreshold
. 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();
+ }
+}
\ No newline at end of file
diff --git a/extras/src/test/java/edu/umd/cs/piccolox/pswing/PSwingTest.java b/extras/src/test/java/edu/umd/cs/piccolox/pswing/PSwingTest.java
index 63d81e8..41eeb18 100644
--- a/extras/src/test/java/edu/umd/cs/piccolox/pswing/PSwingTest.java
+++ b/extras/src/test/java/edu/umd/cs/piccolox/pswing/PSwingTest.java
@@ -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;
}