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

Reply via email to