Revision: 1045
Author: re...@colorado.edu
Date: Wed Aug 25 10:07:08 2010
Log: Added a test PSwingDynamicComponentTest for testing handling of
dynamic pswing content. Fixed issue 163 by improving support for dynamic
resizing of pswing target components. Refactoring of PSwing and
PSwingRepaintManager to simplify and improve readability.
http://code.google.com/p/piccolo2d/source/detail?r=1045
Added:
/piccolo2d.java/branches/release-1.3/extras/src/test/java/edu/umd/cs/piccolox/pswing/PSwingDynamicComponentTest.java
Modified:
/piccolo2d.java/branches/release-1.3/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwing.java
/piccolo2d.java/branches/release-1.3/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwingRepaintManager.java
=======================================
--- /dev/null
+++
/piccolo2d.java/branches/release-1.3/extras/src/test/java/edu/umd/cs/piccolox/pswing/PSwingDynamicComponentTest.java
Wed Aug 25 10:07:08 2010
@@ -0,0 +1,221 @@
+/* Copyright 2010, University of Colorado */
+
+package edu.umd.cs.piccolox.pswing;
+
+import javax.swing.*;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.LineBorder;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Demonstrates a PSwing problem with dynamic JComponents.
+ * <p>
+ * This example shows 2 identical JPanels.
+ * The panel on the left uses PSwing.
+ * The panel on the right uses pure Swing.
+ * <p>
+ * The JPanel contain various JComponents whose text can be updated by
+ * typing into JTextFields and pressing the "Update" button.
+ * The JPanel managed by PSwing is often rendered incorrectly.
+ *
+ * @author Chris Malley (cmal...@pixelzoom.com)
+ */
+public class PSwingDynamicComponentTest extends JFrame {
+
+ private static final Dimension FRAME_SIZE = new Dimension( 800, 400 );
+ private static final int TEXT_FIELD_COLUMNS = 30;
+
+ private final ComponentPanel swingPanel, piccoloPanel;
+ private final JTextField labelTextField, checkBoxTextField,
radioButtonTextField;
+
+ public PSwingDynamicComponentTest() {
+ super( PSwingDynamicComponentTest.class.getName() );
+ setSize( FRAME_SIZE );
+
+ // canvas
+ PSwingCanvas canvas = new PSwingCanvas();
+ canvas.setBackground( Color.RED );
+ canvas.removeInputEventListener( canvas.getZoomEventHandler() );
+ canvas.removeInputEventListener( canvas.getPanEventHandler() );
+
+ // panel that we'll display using Piccolo
+ piccoloPanel = new ComponentPanel();
+ final PSwing pswing = new PSwing( piccoloPanel );
+ canvas.getLayer().addChild( pswing );
+ pswing.setOffset( 10, 10 );
+
+ // panel that we're display using pure Swing
+ swingPanel = new ComponentPanel();
+ JPanel jpanel = new JPanel();
+ jpanel.setBorder( new LineBorder( Color.BLACK ) );
+ jpanel.add( swingPanel );
+
+ // text fields, for specifying dynamic text
+ labelTextField = new JTextField( swingPanel.label.getText(),
TEXT_FIELD_COLUMNS );
+ checkBoxTextField = new JTextField( swingPanel.checkBox.getText(),
TEXT_FIELD_COLUMNS );
+ radioButtonTextField = new JTextField(
swingPanel.radioButton.getText(), TEXT_FIELD_COLUMNS );
+
+ // Update button, for applying dynamic text
+ JButton updateButton = new JButton( "Update" );
+ updateButton.addActionListener( new ActionListener() {
+ public void actionPerformed( ActionEvent e ) {
+ updatePanels();
+ }
+ } );
+
+ //
+ JButton addComponentButton = new JButton( "add component" );
+ addComponentButton.addActionListener( new ActionListener() {
+
+ public void actionPerformed( ActionEvent e ) {
+ piccoloPanel.addComponent( new JLabel( "new" ) );
+ swingPanel.addComponent( new JLabel( "new" ) );
+ }
+
+ });
+
+ // control panel
+ JPanel controlPanel = new JPanel();
+ controlPanel.setBorder( new LineBorder( Color.BLACK ) );
+ controlPanel.setLayout( new GridBagLayout() );
+ GridBagConstraints c = new GridBagConstraints();
+ // JLabel
+ c.gridx = 0;
+ c.gridy = 0;
+ c.anchor = GridBagConstraints.EAST;
+ controlPanel.add( new JLabel( "JLabel text:" ), c );
+ c.gridx++;
+ c.anchor = GridBagConstraints.WEST;
+ controlPanel.add( labelTextField, c );
+ // JCheckBox
+ c.gridx = 0;
+ c.gridy++;
+ c.anchor = GridBagConstraints.EAST;
+ controlPanel.add( new JLabel( "JCheckBox text:" ), c );
+ c.gridx++;
+ c.anchor = GridBagConstraints.WEST;
+ controlPanel.add( checkBoxTextField, c );
+ // JRadioButton
+ c.gridx = 0;
+ c.gridy++;
+ c.anchor = GridBagConstraints.EAST;
+ controlPanel.add( new JLabel( "JRadioButton text:" ), c );
+ c.gridx++;
+ c.anchor = GridBagConstraints.WEST;
+ controlPanel.add( radioButtonTextField, c );
+ // Update button
+ c.gridx = 1;
+ c.gridy++;
+ c.anchor = GridBagConstraints.WEST;
+ controlPanel.add( updateButton, c );
+ // Add component buttons
+ c.gridx = 1;
+ c.gridy++;
+ c.anchor = GridBagConstraints.WEST;
+ controlPanel.add( addComponentButton, c );
+
+
+
+ // main panel
+ JPanel mainPanel = new JPanel( new BorderLayout() );
+ mainPanel.add( canvas, BorderLayout.CENTER );
+ mainPanel.add( jpanel, BorderLayout.EAST );
+ mainPanel.add( controlPanel, BorderLayout.SOUTH );
+ setContentPane( mainPanel );
+ }
+
+ // applies the text field values to the components in the panels
+ private void updatePanels() {
+
+ // Piccolo (PSwing) panel
+ piccoloPanel.label.setText( labelTextField.getText() );
+ piccoloPanel.checkBox.setText( checkBoxTextField.getText() );
+ piccoloPanel.radioButton.setText( radioButtonTextField.getText() );
+
+ // Swing panel
+ swingPanel.label.setText( labelTextField.getText() );
+ swingPanel.checkBox.setText( checkBoxTextField.getText() );
+ swingPanel.radioButton.setText( radioButtonTextField.getText() );
+ }
+
+ // A panel with a few different types of JComponent.
+ private static class ComponentPanel extends JPanel {
+
+ // allow public access to keep our example code short
+ public final JLabel label;
+ public final JCheckBox checkBox;
+ public final JRadioButton radioButton;
+ public final GridBagConstraints constraints;
+
+ public ComponentPanel() {
+ setBorder( new CompoundBorder( new LineBorder( Color.BLACK, 1
), new EmptyBorder( 5, 14, 5, 14 ) ) );
+ setBackground( new Color( 180, 205, 255 ) );
+
+ // components
+ label = new JLabel( "JLabel" );
+ checkBox = new JCheckBox( "JCheckBox" );
+ radioButton = new JRadioButton( "JRadioButton" );
+
+ // layout
+ setLayout( new GridBagLayout() );
+ constraints = new GridBagConstraints();
+ constraints.anchor = GridBagConstraints.WEST;
+ constraints.gridx = 0;
+ constraints.gridy = GridBagConstraints.RELATIVE;
+ addComponent( label );
+ addComponent( checkBox );
+ addComponent( radioButton );
+ }
+
+ public void addComponent( JComponent c ) {
+ add( c, constraints );
+ revalidate();
+ }
+ }
+
+ public static class SleepThread extends Thread {
+
+ public SleepThread( long millis ) {
+ super( new Runnable() {
+ public void run() {
+ while ( true ) {
+ try {
+ SwingUtilities.invokeAndWait( new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep( 1000 );
+ }
+ catch ( InterruptedException e ) {
+ e.printStackTrace();
+ }
+ }
+ } );
+ }
+ catch ( InterruptedException e ) {
+ e.printStackTrace();
+ }
+ catch ( InvocationTargetException e ) {
+ e.printStackTrace();
+ }
+ }
+ }
+ } );
+ }
+ }
+
+ public static void main( String[] args ) {
+ // This thread serves to make the problem more noticeable.
+// new SleepThread( 1000 ).start();
+ SwingUtilities.invokeLater( new Runnable() {
+ public void run() {
+ JFrame frame = new PSwingDynamicComponentTest();
+ frame.setDefaultCloseOperation(
WindowConstants.EXIT_ON_CLOSE );
+ frame.setVisible( true );
+ }
+ } );
+ }
+}
=======================================
---
/piccolo2d.java/branches/release-1.3/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwing.java
Mon Mar 8 16:36:20 2010
+++
/piccolo2d.java/branches/release-1.3/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwing.java
Wed Aug 25 10:07:08 2010
@@ -37,8 +37,6 @@
import java.awt.RenderingHints;
import java.awt.Shape;
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.event.ContainerListener;
@@ -191,6 +189,7 @@
* </p>
*
* @author Sam R. Reid
+ * @author Chris Malley (cmal...@pixelzoom.com)
* @author Benjamin B. Bederson
* @author Lance E. Good
*
@@ -219,7 +218,7 @@
/**
* Default stroke, <code>new BasicStroke()</code>. Cannot be made
static
- * because BasicStroke is not serializable.
+ * because BasicStroke is not serializable. Should not be null.
*/
private Stroke defaultStroke = new BasicStroke();
@@ -270,12 +269,6 @@
};
- private final PropertyChangeListener reshapeListener = new
PropertyChangeListener() {
- public void propertyChange(final PropertyChangeEvent evt) {
- repaint();
- }
- };
-
/**
* Listens to container nodes for changes to its contents. Any
additions
* will automatically have double buffering turned off.
@@ -318,14 +311,6 @@
initializeComponent(component);
component.revalidate();
- //TODO: this listener is suspicious, it's not listening for any
specific property
- component.addPropertyChangeListener(new PropertyChangeListener() {
- /** {...@inheritdoc} */
- public void propertyChange(final PropertyChangeEvent evt) {
- updateBounds();
- }
- });
-
updateBounds();
listenForCanvas(this);
}
@@ -345,24 +330,37 @@
* bounds of this PNode.
*/
public void updateBounds() {
- // 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?
+ /*
+ * Need to explicitly set the component's bounds because
+ * the component's parent (PSwingCanvas.ChildWrapper) has no
layout manager.
+ */
if (componentNeedsResizing()) {
- component.setBounds(0, 0, component.getPreferredSize().width,
component.getPreferredSize().height);
+ updateComponentSize();
}
setBounds(0, 0, component.getPreferredSize().width,
component.getPreferredSize().height);
}
+ /**
+ * Since the parent ChildWrapper has no layout manager, it is the
responsibility of this PSwing
+ * to make sure the component has its bounds set properly, otherwise
it will not be drawn properly.
+ * This method sets the bounds of the component to be equal to its
preferred size.
+ */
+ private void updateComponentSize() {
+ component.setBounds(0, 0, component.getPreferredSize().width,
component.getPreferredSize().height);
+ }
+
+ /**
+ * Determines whether the component should be resized, based on
whether its actual width and height
+ * differ from its preferred width and height.
+ * @return true if the component should be resized.
+ */
private boolean componentNeedsResizing() {
- return component.getWidth() != component.getPreferredSize().width
- || component.getHeight() !=
component.getPreferredSize().height;
+ 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.
+ * Paints the PSwing on the specified renderContext. Also determines
if
+ * the Swing component should be rendered normally or as a filled
rectangle (greeking).
* <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,
@@ -374,25 +372,29 @@
* @param renderContext Contains information about current render.
*/
public void paint(final PPaintContext renderContext) {
+ if (componentNeedsResizing()) {
+ updateComponentSize();
+ component.validate();
+ }
final Graphics2D g2 = renderContext.getGraphics();
- if (defaultStroke == null) {
- defaultStroke = new BasicStroke();
- }
+ //Save Stroke and Font for restoring.
+ Stroke originalStroke = g2.getStroke();
+ Font originalFont = g2.getFont();
g2.setStroke(defaultStroke);
g2.setFont(DEFAULT_FONT);
-
- if (component.getParent() == null) {
- component.revalidate();
- }
-
+
if (shouldRenderGreek(renderContext)) {
paintAsGreek(g2);
}
else {
paint(g2);
}
+
+ //Restore the stroke and font on the Graphics2D
+ g2.setStroke(originalStroke);
+ g2.setFont(originalFont);
}
/**
@@ -408,24 +410,26 @@
}
/**
- * Paints the Swing component as greek.
+ * Paints the Swing component as greek. This method assumes that the
stroke has been set beforehand.
*
* @param g2 The graphics used to render the filled rectangle
*/
public void paintAsGreek(final Graphics2D g2) {
- final Color background = component.getBackground();
- final Color foreground = component.getForeground();
- final Rectangle2D rect = getBounds();
-
- if (background != null) {
- g2.setColor(background);
- }
- g2.fill(rect);
-
- if (foreground != null) {
- g2.setColor(foreground);
- }
- g2.draw(rect);
+ //Save original color for restoring painting as greek.
+ Color originalColor = g2.getColor();
+
+ if (component.getBackground() != null) {
+ g2.setColor(component.getBackground());
+ }
+ g2.fill(getBounds());
+
+ if (component.getForeground() != null) {
+ g2.setColor(component.getForeground());
+ }
+ g2.draw(getBounds());
+
+ //Restore original color on the Graphics2D
+ g2.setColor(originalColor);
}
/** {...@inheritdoc} */
@@ -515,19 +519,6 @@
}
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) {
- updateBounds();
- }
-
- public void componentShown(final ComponentEvent e) {
- updateBounds();
- }
- });
-
if (c instanceof Container) {
initializeChildren((Container) c);
((Container) c).addContainerListener(doubleBufferRemover);
@@ -663,7 +654,7 @@
* threshold the Swing component is rendered as 'Greek' instead of
painting
* the Swing component. Defaults to {...@link #DEFAULT_GREEK_THRESHOLD}.
*
- * @see PSwing#paintGreek(PPaintContext)
+ * @see PSwing#paintAsGreek(Graphics2D)
* @return the current Greek threshold scale
*/
public double getGreekThreshold() {
@@ -675,7 +666,7 @@
* scale will be below this threshold the Swing component is rendered
as
* 'Greek' instead of painting the Swing component..
*
- * @see PSwing#paintGreek(PPaintContext)
+ * @see PSwing#paintAsGreek(Graphics2D)
* @param greekThreshold Greek threshold in scale
*/
public void setGreekThreshold(final double greekThreshold) {
=======================================
---
/piccolo2d.java/branches/release-1.3/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwingRepaintManager.java
Mon Jan 18 16:07:38 2010
+++
/piccolo2d.java/branches/release-1.3/extras/src/main/java/edu/umd/cs/piccolox/pswing/PSwingRepaintManager.java
Wed Aug 25 10:07:08 2010
@@ -33,13 +33,13 @@
import javax.swing.JComponent;
import javax.swing.RepaintManager;
-import javax.swing.SwingUtilities;
import edu.umd.cs.piccolo.util.PBounds;
/**
* This RepaintManager replaces the default Swing implementation, and is
used to
- * intercept and repaint dirty regions of PSwing components.
+ * repaint dirty regions of PSwing components and make sure the PSwings
have
+ * the appropriate size.
* <p>
* This is an internal class used by Piccolo to support Swing components in
* Piccolo. This should not be instantiated, though all the public methods
of
@@ -49,12 +49,12 @@
* <p>
* PBasicRepaint Manager is an extension of RepaintManager that traps those
* repaints called by the Swing components that have been added to the
PCanvas
- * and passes these repaints to the SwingVisualComponent rather than up the
+ * and passes these repaints to the PSwing rather than up the
* component hierarchy as usually happens.
* </p>
* <p>
- * Also traps revalidate calls made by the Swing components added to the
PCanvas
- * to reshape the applicable Visual Component.
+ * Also traps invalidate calls made by the Swing components added to the
PCanvas
+ * to reshape the corresponding PSwing.
* </p>
* <p>
* Also keeps a list of PSwings that are painting. This disables repaint
until
@@ -62,14 +62,14 @@
* by Swing's CellRendererPane which is itself a work-around. The problem
is
* that JTable's, JTree's, and JList's cell renderers need to be validated
* before repaint. Since we have to repaint the entire Swing component
hierarchy
- * (in the case of a Swing component group used as a Piccolo visual
component).
- * This causes an infinite loop. So we introduce the restriction that no
- * repaints can be triggered by a call to paint.
+ * (in the case of a PSwing), this causes an infinite loop. So we
introduce the
+ * restriction that no repaints can be triggered by a call to paint.
* </p>
*
* @author Benjamin B. Bederson
* @author Lance E. Good
* @author Sam R. Reid
+ * @author Chris Malley (cmal...@pixelzoom.com)
*/
public class PSwingRepaintManager extends RepaintManager {
@@ -119,19 +119,15 @@
* @param width Width of the dirty region in the component
* @param height Height of the dirty region in the component
*/
- public synchronized void addDirtyRegion(final JComponent component,
final int x, final int y, final int width,
- final int height) {
+ public synchronized void addDirtyRegion(final JComponent component,
final int x, final int y, final int width, final int height) {
boolean captureRepaint = false;
JComponent childComponent = null;
-
int captureX = x;
int captureY = y;
- // We have to check to see if the PCanvas
- // (ie. the SwingWrapper) is in the components ancestry. If so,
- // we will want to capture that repaint. However, we also will
- // need to translate the repaint request since the component may
- // be offset inside another component.
+ // We have to check to see if the PCanvas (ie. the SwingWrapper)
is in the components ancestry. If so, we will
+ // want to capture that repaint. However, we also will need to
translate the repaint request since the component
+ // may be offset inside another component.
for (Component comp = component; comp != null &&
comp.isLightweight(); comp = comp.getParent()) {
if (comp.getParent() instanceof PSwingCanvas.ChildWrapper) {
captureRepaint = true;
@@ -145,34 +141,24 @@
}
}
- // Now we check to see if we should capture the repaint and act
- // accordingly
+ // Now we check to see if we should capture the repaint and act
accordingly
if (captureRepaint) {
if (!isPainting(childComponent)) {
final double repaintW = Math.min(childComponent.getWidth()
- captureX, width);
final double repaintH =
Math.min(childComponent.getHeight() - captureY, height);
- dispatchRepaint(childComponent, new PBounds(captureX,
captureY, repaintW, repaintH));
+ //Schedule a repaint for the dirty part of the PSwing
+ getPSwing(childComponent).repaint(new PBounds(captureX,
captureY, repaintW, repaintH));
}
}
else {
super.addDirtyRegion(component, x, y, width, height);
}
}
-
- private void dispatchRepaint(final JComponent childComponent, final
PBounds repaintBounds) {
- final PSwing pSwing = (PSwing)
childComponent.getClientProperty(PSwing.PSWING_PROPERTY);
-
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- pSwing.repaint(repaintBounds);
- }
- });
- }
/**
- * This is the method "revalidate" calls in the Swing components.
Overridden
- * to capture revalidate calls from those Swing components being used
as
+ * This is the method "invalidate" calls in the Swing components.
Overridden
+ * to capture invalidation calls from those Swing components being
used as
* Piccolo visual components and to update Piccolo's visual component
* wrapper bounds (these are stored separately from the Swing
component).
* Otherwise, behaves like the superclass.
@@ -180,20 +166,21 @@
* @param invalidComponent The Swing component that needs validation
*/
public synchronized void addInvalidComponent(final JComponent
invalidComponent) {
- final JComponent capturedComponent = invalidComponent;
-
- if (capturedComponent.getParent() == null
- || !(capturedComponent.getParent() instanceof
PSwingCanvas.ChildWrapper)) {
+ if (invalidComponent.getParent() == null |
| !(invalidComponent.getParent() instanceof PSwingCanvas.ChildWrapper)) {
super.addInvalidComponent(invalidComponent);
}
else {
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- capturedComponent.validate();
- final PSwing pSwing = (PSwing)
capturedComponent.getClientProperty(PSwing.PSWING_PROPERTY);
- pSwing.updateBounds();
- }
- });
+ invalidComponent.validate();
+ getPSwing(invalidComponent).updateBounds();
}
}
-}
+
+ /**
+ * Obtains the PSwing associated with the specified component.
+ * @param component the component for which to return the associated
PSwing
+ * @return the associated PSwing
+ */
+ private PSwing getPSwing(JComponent component) {
+ return (PSwing) component.getClientProperty(
PSwing.PSWING_PROPERTY );
+ }
+}
--
Piccolo2D Developers Group: http://groups.google.com/group/piccolo2d-dev?hl=en