diff --git a/core/src/build/conf/checkstyle.xml b/core/src/build/conf/checkstyle.xml
index 446472a..c4f2ce0 100644
--- a/core/src/build/conf/checkstyle.xml
+++ b/core/src/build/conf/checkstyle.xml
@@ -36,10 +36,6 @@
     
     
* Cameras are also the point through which all PInputEvents enter Piccolo. The - * canvas coordinate system, and the local coordinate system of the topmost camera - * should always be the same. + * canvas coordinate system, and the local coordinate system of the topmost + * camera should always be the same. *
+ * * @see PLayer * @version 1.0 * @author Jesse Grosjean */ public class PCamera extends PNode { - - /** - * The property name that identifies a change in the set of this camera's - * layers (see {@link #getLayer getLayer}, {@link #getLayerCount - * getLayerCount}, {@link #getLayersReference getLayersReference}). A - * property change event's new value will be a reference to the list of this - * nodes layers, but old value will always be null. - */ - public static final String PROPERTY_LAYERS = "layers"; + + /** + * The property name that identifies a change in the set of this camera's + * layers (see {@link #getLayer getLayer}, {@link #getLayerCount + * getLayerCount}, {@link #getLayersReference getLayersReference}). A + * property change event's new value will be a reference to the list of this + * nodes layers, but old value will always be null. + */ + public static final String PROPERTY_LAYERS = "layers"; public static final int PROPERTY_CODE_LAYERS = 1 << 11; - - /** - * The property name that identifies a change in this camera's view - * transform (see {@link #getViewTransform getViewTransform}, {@link - * #getViewTransformReference getViewTransformReference}). A property change - * event's new value will be a reference to the view transform, but old - * value will always be null. - */ - public static final String PROPERTY_VIEW_TRANSFORM = "viewTransform"; + + /** + * The property name that identifies a change in this camera's view + * transform (see {@link #getViewTransform getViewTransform}, + * {@link #getViewTransformReference getViewTransformReference}). A property + * change event's new value will be a reference to the view transform, but + * old value will always be null. + */ + public static final String PROPERTY_VIEW_TRANSFORM = "viewTransform"; public static final int PROPERTY_CODE_VIEW_TRANSFORM = 1 << 12; - - public static final int VIEW_CONSTRAINT_NONE = 0; - public static final int VIEW_CONSTRAINT_ALL = 1; - public static final int VIEW_CONSTRAINT_CENTER = 2; - - private transient PComponent component; - private transient List layers; - private PAffineTransform viewTransform; - private int viewConstraint; - - /** - * Construct a new camera with no layers and a default white color. - */ - public PCamera() { - super(); - viewTransform = new PAffineTransform(); - layers = new ArrayList(); - viewConstraint = VIEW_CONSTRAINT_NONE; - } - /** - * Get the canvas associated with this camera. This will return null if - * not canvas has been associated, as may be the case for internal cameras. - */ - public PComponent getComponent() { - return component; - } - - /** - * Set the canvas associated with this camera. When the camera is repainted - * it will request repaints on this canvas. - */ - public void setComponent(PComponent aComponent) { - component = aComponent; - invalidatePaint(); - } - - /** - * Repaint this camera, and forward the repaint request to the camera's - * canvas if it is not null. - */ - public void repaintFrom(PBounds localBounds, PNode descendentOrThis) { - if (getParent() != null) { - if (descendentOrThis != this) { - localToParent(localBounds); - } - - if (component != null) { - component.repaint(localBounds); - } - - getParent().repaintFrom(localBounds, this); - } - } + public static final int VIEW_CONSTRAINT_NONE = 0; + public static final int VIEW_CONSTRAINT_ALL = 1; + public static final int VIEW_CONSTRAINT_CENTER = 2; - private static PBounds TEMP_REPAINT_RECT = new PBounds(); - - /** - * Repaint from one of the cameras layers. The repaint region needs to be - * transformed from view to local in this case. Unlike most repaint - * methods in piccolo this one must not modify the viewBounds parameter. - */ - public void repaintFromLayer(PBounds viewBounds, PNode repaintedLayer) { - TEMP_REPAINT_RECT.setRect(viewBounds); - - viewToLocal(TEMP_REPAINT_RECT); - if (getBoundsReference().intersects(TEMP_REPAINT_RECT)) { - PBounds.intersect(TEMP_REPAINT_RECT, getBoundsReference(), TEMP_REPAINT_RECT); - repaintFrom(TEMP_REPAINT_RECT, repaintedLayer); - } - } - - //**************************************************************** - // Layers - //**************************************************************** - - /** - * Return a reference to the list of layers managed by this camera. - */ - public List getLayersReference() { - return layers; - } - - public int getLayerCount() { - return layers.size(); - } + private transient PComponent component; + private transient List layers; + private PAffineTransform viewTransform; + private int viewConstraint; - public PLayer getLayer(int index) { - return (PLayer) layers.get(index); - } - - public int indexOfLayer(PLayer layer) { - return layers.indexOf(layer); - } + /** + * Construct a new camera with no layers and a default white color. + */ + public PCamera() { + super(); + viewTransform = new PAffineTransform(); + layers = new ArrayList(); + viewConstraint = VIEW_CONSTRAINT_NONE; + } - /** - * Add the layer to the end of this camera's list of layers. - * Layers may be viewed by multiple cameras at once. - */ - public void addLayer(PLayer layer) { - addLayer(layers.size(), layer); - } + /** + * Get the canvas associated with this camera. This will return null if not + * canvas has been associated, as may be the case for internal cameras. + */ + public PComponent getComponent() { + return component; + } - /** - * Add the layer at the given index in this camera's list of layers. - * Layers may be viewed by multiple cameras at once. - */ - public void addLayer(int index, PLayer layer) { - layers.add(index, layer); - layer.addCamera(this); - invalidatePaint(); - firePropertyChange(PROPERTY_CODE_LAYERS ,PROPERTY_LAYERS, null, layers); - } - - /** - * Remove the given layer from the list of layers managed by this - * camera. - */ - public PLayer removeLayer(PLayer layer) { - return removeLayer(layers.indexOf(layer)); - } - - /** - * Remove the layer at the given index from the list of - * layers managed by this camera. - */ - public PLayer removeLayer(int index) { - PLayer layer = (PLayer) layers.remove(index); - layer.removeCamera(this); - invalidatePaint(); - firePropertyChange(PROPERTY_CODE_LAYERS, PROPERTY_LAYERS, null, layers); - return layer; - } - - /** - * Return the total bounds of all the layers that this camera looks at. - */ - public PBounds getUnionOfLayerFullBounds() { - PBounds result = new PBounds(); - - int count = getLayerCount(); - for (int i = 0; i < count; i++) { - PLayer each = (PLayer) layers.get(i); - result.add(each.getFullBoundsReference()); - } - - return result; - } - - //**************************************************************** - // Painting Layers - //**************************************************************** - - /** - * Paint this camera (default background color is white) and then paint - * the cameras view through the view transform. - */ - protected void paint(PPaintContext paintContext) { - super.paint(paintContext); - - paintContext.pushClip(getBoundsReference()); - paintContext.pushTransform(viewTransform); + /** + * Set the canvas associated with this camera. When the camera is repainted + * it will request repaints on this canvas. + */ + public void setComponent(PComponent aComponent) { + component = aComponent; + invalidatePaint(); + } - paintCameraView(paintContext); - paintDebugInfo(paintContext); - - paintContext.popTransform(viewTransform); - paintContext.popClip(getBoundsReference()); - } - - /** - * Paint all the layers that the camera is looking at, this method is - * only called when the cameras view transform and clip are applied - * to the paintContext. - */ - protected void paintCameraView(PPaintContext paintContext) { - int count = getLayerCount(); - for (int i = 0; i < count; i++) { - PLayer each = (PLayer) layers.get(i); - each.fullPaint(paintContext); - } - } + /** + * Repaint this camera, and forward the repaint request to the camera's + * canvas if it is not null. + */ + public void repaintFrom(PBounds localBounds, PNode descendentOrThis) { + if (getParent() != null) { + if (descendentOrThis != this) { + localToParent(localBounds); + } - protected void paintDebugInfo(PPaintContext paintContext) { - if (PDebug.debugBounds || PDebug.debugFullBounds) { - Graphics2D g2 = paintContext.getGraphics(); - paintContext.setRenderQuality(PPaintContext.LOW_QUALITY_RENDERING); - g2.setStroke(new BasicStroke(0)); - ArrayList nodes = new ArrayList(); - PBounds nodeBounds = new PBounds(); - - Color boundsColor = Color.red; - Color fullBoundsColor = new Color(1.0f, 0f, 0f, 0.2f); - - for (int i = 0; i < getLayerCount(); i++) { - getLayer(i).getAllNodes(null, nodes); - } - - Iterator i = getAllNodes(null, nodes).iterator(); - - while (i.hasNext()) { - PNode each = (PNode) i.next(); - - if (PDebug.debugBounds) { - g2.setPaint(boundsColor); - nodeBounds.setRect(each.getBoundsReference()); - - if (!nodeBounds.isEmpty()) { - each.localToGlobal(nodeBounds); - globalToLocal(nodeBounds); - if (each == this || each.isDescendentOf(this)) { - localToView(nodeBounds); - } - g2.draw(nodeBounds); - } - } - - if (PDebug.debugFullBounds) { - g2.setPaint(fullBoundsColor); - nodeBounds.setRect(each.getFullBoundsReference()); + if (component != null) { + component.repaint(localBounds); + } - if (!nodeBounds.isEmpty()) { - if (each.getParent() != null) { - each.getParent().localToGlobal(nodeBounds); - } - globalToLocal(nodeBounds); - if (each == this || each.isDescendentOf(this)) { - localToView(nodeBounds); - } - g2.fill(nodeBounds); - } - } - } - } - } + getParent().repaintFrom(localBounds, this); + } + } - /** - * Override fullPaint to push the camera onto the paintContext so that it - * can be later be accessed by PPaintContext.getCamera(); - */ - public void fullPaint(PPaintContext paintContext) { - paintContext.pushCamera(this); - super.fullPaint(paintContext); - paintContext.popCamera(this); - } - - //**************************************************************** - // Picking - //**************************************************************** + private static PBounds TEMP_REPAINT_RECT = new PBounds(); - /** - * Generate and return a PPickPath for the point x,y specified in the local - * coord system of this camera. Picking is done with a rectangle, halo - * specifies how large that rectangle will be. - */ - public PPickPath pick(double x, double y, double halo) { - PBounds b = new PBounds(new Point2D.Double(x, y), -halo, -halo); - PPickPath result = new PPickPath(this, b); - - fullPick(result); - - // make sure this camera is pushed. - if (result.getNodeStackReference().size() == 0) { - result.pushNode(this); - result.pushTransform(getTransformReference(false)); - } - - return result; - } - - /** - * After the direct children of the camera have been given a chance to be - * picked objects viewed by the camera are given a chance to be picked. - */ - protected boolean pickAfterChildren(PPickPath pickPath) { - if (intersects(pickPath.getPickBounds())) { - pickPath.pushTransform(viewTransform); - - if (pickCameraView(pickPath)) { - return true; - } - - pickPath.popTransform(viewTransform); - return true; - } - return false; - } - - /** - * Pick all the layers that the camera is looking at, this method is - * only called when the cameras view transform and clip are applied - * to the pickPath. - */ - protected boolean pickCameraView(PPickPath pickPath) { - int count = getLayerCount(); - for (int i = count - 1; i >= 0; i--) { - PLayer each = (PLayer) layers.get(i); - if (each.fullPick(pickPath)) { - return true; - } - } - return false; - } - - - //**************************************************************** - // View Transform - Methods for accessing the view transform. The - // view transform is applied before painting and picking the cameras - // layers. But not before painting or picking its direct children. - // - // Changing the view transform is how zooming and panning are - // accomplished. - //**************************************************************** + /** + * Repaint from one of the cameras layers. The repaint region needs to be + * transformed from view to local in this case. Unlike most repaint methods + * in piccolo this one must not modify the viewBounds parameter. + */ + public void repaintFromLayer(PBounds viewBounds, PNode repaintedLayer) { + TEMP_REPAINT_RECT.setRect(viewBounds); - /** - * Return the bounds of this camera in the view coordinate system. - */ - public PBounds getViewBounds() { - return (PBounds) localToView(getBounds()); - } - - /** - * Translates and scales the camera's view transform so that the given bounds (in camera - * layer's coordinate system)are centered withing the cameras view bounds. Use this method - * to point the camera at a given location. - */ - public void setViewBounds(Rectangle2D centerBounds) { - animateViewToCenterBounds(centerBounds, true, 0); - } + viewToLocal(TEMP_REPAINT_RECT); + if (getBoundsReference().intersects(TEMP_REPAINT_RECT)) { + PBounds.intersect(TEMP_REPAINT_RECT, getBoundsReference(), TEMP_REPAINT_RECT); + repaintFrom(TEMP_REPAINT_RECT, repaintedLayer); + } + } - /** - * Return the scale applied by the view transform to the layers - * viewed by this camera. - */ - public double getViewScale() { - return viewTransform.getScale(); - } + // **************************************************************** + // Layers + // **************************************************************** - /** - * Scale the view transform that is applied to the layers - * viewed by this camera by the given amount. - */ - public void scaleView(double scale) { - scaleViewAboutPoint(scale, 0, 0); - } + /** + * Return a reference to the list of layers managed by this camera. + */ + public List getLayersReference() { + return layers; + } - /** - * Scale the view transform that is applied to the layers - * viewed by this camera by the given amount about the given point. - */ - public void scaleViewAboutPoint(double scale, double x, double y) { - viewTransform.scaleAboutPoint(scale, x, y); - applyViewConstraints(); - invalidatePaint(); - firePropertyChange(PROPERTY_CODE_VIEW_TRANSFORM, PROPERTY_VIEW_TRANSFORM, null, viewTransform); - } + public int getLayerCount() { + return layers.size(); + } - /** - * Set the scale of the view transform that is applied to - * the layers viewed by this camera. - */ - public void setViewScale(double scale) { - scaleView(scale / getViewScale()); - } + public PLayer getLayer(int index) { + return (PLayer) layers.get(index); + } - /** - * Translate the view transform that is applied to the camera's - * layers. - */ - public void translateView(double dx, double dy) { - viewTransform.translate(dx, dy); - applyViewConstraints(); - invalidatePaint(); - firePropertyChange(PROPERTY_CODE_VIEW_TRANSFORM, PROPERTY_VIEW_TRANSFORM, null, viewTransform); - } + public int indexOfLayer(PLayer layer) { + return layers.indexOf(layer); + } - /** - * Sets the offset of the view transform that is applied - * to the camera's layers. - */ - public void setViewOffset(double x, double y) { - viewTransform.setOffset(x, y); - applyViewConstraints(); - invalidatePaint(); - firePropertyChange(PROPERTY_CODE_VIEW_TRANSFORM, PROPERTY_VIEW_TRANSFORM, null, viewTransform); - } + /** + * Add the layer to the end of this camera's list of layers. Layers may be + * viewed by multiple cameras at once. + */ + public void addLayer(PLayer layer) { + addLayer(layers.size(), layer); + } - /** - * Get a copy of the view transform that is applied to the camera's - * layers. - */ - public PAffineTransform getViewTransform() { - return (PAffineTransform) viewTransform.clone(); - } + /** + * Add the layer at the given index in this camera's list of layers. Layers + * may be viewed by multiple cameras at once. + */ + public void addLayer(int index, PLayer layer) { + layers.add(index, layer); + layer.addCamera(this); + invalidatePaint(); + firePropertyChange(PROPERTY_CODE_LAYERS, PROPERTY_LAYERS, null, layers); + } - /** - * Get a reference to the view transform that is applied to the camera's - * layers. - */ - public PAffineTransform getViewTransformReference() { - return viewTransform; - } - - /** - * Set the view transform that is applied to the views layers. - */ - public void setViewTransform(AffineTransform aTransform) { - viewTransform.setTransform(aTransform); - applyViewConstraints(); - invalidatePaint(); - firePropertyChange(PROPERTY_CODE_VIEW_TRANSFORM, PROPERTY_VIEW_TRANSFORM, null, viewTransform); - } + /** + * Remove the given layer from the list of layers managed by this camera. + */ + public PLayer removeLayer(PLayer layer) { + return removeLayer(layers.indexOf(layer)); + } - /** - * Animate the camera's view from its current transform when the activity - * starts to a new transform that centers the given bounds in the camera - * layers coordinate system into the cameras view bounds. If the duration is - * 0 then the view will be transformed immediately, and null will be - * returned. Else a new PTransformActivity will get returned that is set to - * animate the camera's view transform to the new bounds. If shouldScale - * is true, then the camera will also scale its view so that the given - * bounds fit fully within the cameras view bounds, else the camera will - * maintain its original scale. - */ - public PTransformActivity animateViewToCenterBounds(Rectangle2D centerBounds, boolean shouldScaleToFit, long duration) { - PBounds viewBounds = getViewBounds(); - PDimension delta = viewBounds.deltaRequiredToCenter(centerBounds); - PAffineTransform newTransform = getViewTransform(); - newTransform.translate(delta.width, delta.height); - - if (shouldScaleToFit) { - double s = Math.min(viewBounds.getWidth() / centerBounds.getWidth(), viewBounds.getHeight() / centerBounds.getHeight()); - if (s != Double.POSITIVE_INFINITY && s != 0) { - newTransform.scaleAboutPoint(s, centerBounds.getCenterX(), centerBounds.getCenterY()); - } - } + /** + * Remove the layer at the given index from the list of layers managed by + * this camera. + */ + public PLayer removeLayer(int index) { + PLayer layer = (PLayer) layers.remove(index); + layer.removeCamera(this); + invalidatePaint(); + firePropertyChange(PROPERTY_CODE_LAYERS, PROPERTY_LAYERS, null, layers); + return layer; + } - return animateViewToTransform(newTransform, duration); - } + /** + * Return the total bounds of all the layers that this camera looks at. + */ + public PBounds getUnionOfLayerFullBounds() { + PBounds result = new PBounds(); - /** - * Pan the camera's view from its current transform when the activity starts - * to a new transform so that the view bounds will contain (if possible, intersect - * if not possible) the new bounds in the camera layers coordinate system. - * If the duration is 0 then the view will be transformed immediately, and null - * will be returned. Else a new PTransformActivity will get returned that is set - * to animate the camera's view transform to the new bounds. - */ - public PTransformActivity animateViewToPanToBounds(Rectangle2D panToBounds, long duration) { - PBounds viewBounds = getViewBounds(); - PDimension delta = viewBounds.deltaRequiredToContain(panToBounds); - - if (delta.width != 0 || delta.height != 0) { - if (duration == 0) { - translateView(-delta.width, -delta.height); - } else { - AffineTransform at = getViewTransform(); - at.translate(-delta.width, -delta.height); - return animateViewToTransform(at, duration); - } - } + int count = getLayerCount(); + for (int i = 0; i < count; i++) { + PLayer each = (PLayer) layers.get(i); + result.add(each.getFullBoundsReference()); + } - return null; - } + return result; + } - /** - * @deprecated Renamed to animateViewToPanToBounds - */ - public PTransformActivity animateViewToIncludeBounds(Rectangle2D includeBounds, long duration) { - return animateViewToPanToBounds(includeBounds, duration); - } - - /** - * Animate the cameras view transform from its current value when the - * activity starts to the new destination transform value. - */ - public PTransformActivity animateViewToTransform(AffineTransform destination, long duration) { - if (duration == 0) { - setViewTransform(destination); - return null; - } - - PTransformActivity.Target t = new PTransformActivity.Target() { - public void setTransform(AffineTransform aTransform) { - PCamera.this.setViewTransform(aTransform); - } - public void getSourceMatrix(double[] aSource) { - PCamera.this.viewTransform.getMatrix(aSource); - } - }; - - PTransformActivity ta = new PTransformActivity(duration, PUtil.DEFAULT_ACTIVITY_STEP_RATE, t, destination); - - PRoot r = getRoot(); - if (r != null) { - r.getActivityScheduler().addActivity(ta); - } - - return ta; - } + // **************************************************************** + // Painting Layers + // **************************************************************** - //**************************************************************** - // View Transform Constraints - Methods for setting and applying - // constraints to the view transform. - //**************************************************************** - - public int getViewConstraint() { - return viewConstraint; - } - public void setViewConstraint(int constraint) { - viewConstraint = constraint; - applyViewConstraints(); - } - - protected void applyViewConstraints() { - if (viewConstraint == VIEW_CONSTRAINT_NONE) - return; + /** + * Paint this camera (default background color is white) and then paint the + * cameras view through the view transform. + */ + protected void paint(PPaintContext paintContext) { + super.paint(paintContext); - PBounds viewBounds = getViewBounds(); - PBounds layerBounds = (PBounds) globalToLocal(getUnionOfLayerFullBounds()); - PDimension constraintDelta = null; - - switch (viewConstraint) { - case VIEW_CONSTRAINT_ALL: - constraintDelta = viewBounds.deltaRequiredToContain(layerBounds); - break; + paintContext.pushClip(getBoundsReference()); + paintContext.pushTransform(viewTransform); - case VIEW_CONSTRAINT_CENTER: - layerBounds.setRect(layerBounds.getCenterX(), layerBounds.getCenterY(), 0, 0); - constraintDelta = viewBounds.deltaRequiredToContain(layerBounds); - break; - } - - viewTransform.translate(-constraintDelta.width, -constraintDelta.height); - } + paintCameraView(paintContext); + paintDebugInfo(paintContext); - //**************************************************************** - // Camera View Coord System Conversions - Methods to translate from - // the camera's local coord system (above the camera's view transform) to the - // camera view coord system (below the camera's view transform). When - // converting geometry from one of the canvas's layers you must go - // through the view transform. - //**************************************************************** + paintContext.popTransform(viewTransform); + paintContext.popClip(getBoundsReference()); + } - /** - * Convert the point from the camera's view coordinate system to the - * camera's local coordinate system. The given point is modified by this. - */ - public Point2D viewToLocal(Point2D viewPoint) { - return viewTransform.transform(viewPoint, viewPoint); - } + /** + * Paint all the layers that the camera is looking at, this method is only + * called when the cameras view transform and clip are applied to the + * paintContext. + */ + protected void paintCameraView(PPaintContext paintContext) { + int count = getLayerCount(); + for (int i = 0; i < count; i++) { + PLayer each = (PLayer) layers.get(i); + each.fullPaint(paintContext); + } + } - /** - * Convert the dimension from the camera's view coordinate system to the - * camera's local coordinate system. The given dimension is modified by this. - */ - public Dimension2D viewToLocal(Dimension2D viewDimension) { - return viewTransform.transform(viewDimension, viewDimension); - } + protected void paintDebugInfo(PPaintContext paintContext) { + if (PDebug.debugBounds || PDebug.debugFullBounds) { + Graphics2D g2 = paintContext.getGraphics(); + paintContext.setRenderQuality(PPaintContext.LOW_QUALITY_RENDERING); + g2.setStroke(new BasicStroke(0)); + ArrayList nodes = new ArrayList(); + PBounds nodeBounds = new PBounds(); - /** - * Convert the rectangle from the camera's view coordinate system to the - * camera's local coordinate system. The given rectangle is modified by this method. - */ - public Rectangle2D viewToLocal(Rectangle2D viewRectangle) { - return viewTransform.transform(viewRectangle, viewRectangle); - } + Color boundsColor = Color.red; + Color fullBoundsColor = new Color(1.0f, 0f, 0f, 0.2f); - /** - * Convert the point from the camera's local coordinate system to the - * camera's view coordinate system. The given point is modified by this method. - */ - public Point2D localToView(Point2D localPoint) { - try { - return viewTransform.inverseTransform(localPoint, localPoint); - } catch (NoninvertibleTransformException e) { - e.printStackTrace(); - } - return null; - } + for (int i = 0; i < getLayerCount(); i++) { + getLayer(i).getAllNodes(null, nodes); + } - /** - * Convert the dimension from the camera's local coordinate system to the - * camera's view coordinate system. The given dimension is modified by this method. - */ - public Dimension2D localToView(Dimension2D localDimension) { - return viewTransform.inverseTransform(localDimension, localDimension); - } + Iterator i = getAllNodes(null, nodes).iterator(); - /** - * Convert the rectangle from the camera's local coordinate system to the - * camera's view coordinate system. The given rectangle is modified by this method. - */ - public Rectangle2D localToView(Rectangle2D localRectangle) { - return viewTransform.inverseTransform(localRectangle, localRectangle); - } - - //**************************************************************** - // Serialization - Cameras conditionally serialize their layers. - // This means that only the layer references that were unconditionally - // (using writeObject) serialized by someone else will be restored - // when the camera is unserialized. - //****************************************************************/ - - /** - * Write this camera and all its children out to the given stream. Note - * that the cameras layers are written conditionally, so they will only - * get written out if someone else writes them unconditionally. - */ - private void writeObject(ObjectOutputStream out) throws IOException { - out.defaultWriteObject(); - - int count = getLayerCount(); - for (int i = 0; i < count; i++) { - ((PObjectOutputStream)out).writeConditionalObject(layers.get(i)); - } - - out.writeObject(Boolean.FALSE); - ((PObjectOutputStream)out).writeConditionalObject(component); - } + while (i.hasNext()) { + PNode each = (PNode) i.next(); - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - - layers = new ArrayList(); + if (PDebug.debugBounds) { + g2.setPaint(boundsColor); + nodeBounds.setRect(each.getBoundsReference()); - while (true) { - Object each = in.readObject(); - if (each != null) { - if (each.equals(Boolean.FALSE)) { - break; - } else { - layers.add(each); - } - } - } - - component = (PComponent) in.readObject(); - } + if (!nodeBounds.isEmpty()) { + each.localToGlobal(nodeBounds); + globalToLocal(nodeBounds); + if (each == this || each.isDescendentOf(this)) { + localToView(nodeBounds); + } + g2.draw(nodeBounds); + } + } + + if (PDebug.debugFullBounds) { + g2.setPaint(fullBoundsColor); + nodeBounds.setRect(each.getFullBoundsReference()); + + if (!nodeBounds.isEmpty()) { + if (each.getParent() != null) { + each.getParent().localToGlobal(nodeBounds); + } + globalToLocal(nodeBounds); + if (each == this || each.isDescendentOf(this)) { + localToView(nodeBounds); + } + g2.fill(nodeBounds); + } + } + } + } + } + + /** + * Override fullPaint to push the camera onto the paintContext so that it + * can be later be accessed by PPaintContext.getCamera(); + */ + public void fullPaint(PPaintContext paintContext) { + paintContext.pushCamera(this); + super.fullPaint(paintContext); + paintContext.popCamera(this); + } + + // **************************************************************** + // Picking + // **************************************************************** + + /** + * Generate and return a PPickPath for the point x,y specified in the local + * coord system of this camera. Picking is done with a rectangle, halo + * specifies how large that rectangle will be. + */ + public PPickPath pick(double x, double y, double halo) { + PBounds b = new PBounds(new Point2D.Double(x, y), -halo, -halo); + PPickPath result = new PPickPath(this, b); + + fullPick(result); + + // make sure this camera is pushed. + if (result.getNodeStackReference().size() == 0) { + result.pushNode(this); + result.pushTransform(getTransformReference(false)); + } + + return result; + } + + /** + * After the direct children of the camera have been given a chance to be + * picked objects viewed by the camera are given a chance to be picked. + */ + protected boolean pickAfterChildren(PPickPath pickPath) { + if (intersects(pickPath.getPickBounds())) { + pickPath.pushTransform(viewTransform); + + if (pickCameraView(pickPath)) { + return true; + } + + pickPath.popTransform(viewTransform); + return true; + } + return false; + } + + /** + * Pick all the layers that the camera is looking at, this method is only + * called when the cameras view transform and clip are applied to the + * pickPath. + */ + protected boolean pickCameraView(PPickPath pickPath) { + int count = getLayerCount(); + for (int i = count - 1; i >= 0; i--) { + PLayer each = (PLayer) layers.get(i); + if (each.fullPick(pickPath)) { + return true; + } + } + return false; + } + + // **************************************************************** + // View Transform - Methods for accessing the view transform. The + // view transform is applied before painting and picking the cameras + // layers. But not before painting or picking its direct children. + // + // Changing the view transform is how zooming and panning are + // accomplished. + // **************************************************************** + + /** + * Return the bounds of this camera in the view coordinate system. + */ + public PBounds getViewBounds() { + return (PBounds) localToView(getBounds()); + } + + /** + * Translates and scales the camera's view transform so that the given + * bounds (in camera layer's coordinate system)are centered withing the + * cameras view bounds. Use this method to point the camera at a given + * location. + */ + public void setViewBounds(Rectangle2D centerBounds) { + animateViewToCenterBounds(centerBounds, true, 0); + } + + /** + * Return the scale applied by the view transform to the layers viewed by + * this camera. + */ + public double getViewScale() { + return viewTransform.getScale(); + } + + /** + * Scale the view transform that is applied to the layers viewed by this + * camera by the given amount. + */ + public void scaleView(double scale) { + scaleViewAboutPoint(scale, 0, 0); + } + + /** + * Scale the view transform that is applied to the layers viewed by this + * camera by the given amount about the given point. + */ + public void scaleViewAboutPoint(double scale, double x, double y) { + viewTransform.scaleAboutPoint(scale, x, y); + applyViewConstraints(); + invalidatePaint(); + firePropertyChange(PROPERTY_CODE_VIEW_TRANSFORM, PROPERTY_VIEW_TRANSFORM, null, viewTransform); + } + + /** + * Set the scale of the view transform that is applied to the layers viewed + * by this camera. + */ + public void setViewScale(double scale) { + scaleView(scale / getViewScale()); + } + + /** + * Translate the view transform that is applied to the camera's layers. + */ + public void translateView(double dx, double dy) { + viewTransform.translate(dx, dy); + applyViewConstraints(); + invalidatePaint(); + firePropertyChange(PROPERTY_CODE_VIEW_TRANSFORM, PROPERTY_VIEW_TRANSFORM, null, viewTransform); + } + + /** + * Sets the offset of the view transform that is applied to the camera's + * layers. + */ + public void setViewOffset(double x, double y) { + viewTransform.setOffset(x, y); + applyViewConstraints(); + invalidatePaint(); + firePropertyChange(PROPERTY_CODE_VIEW_TRANSFORM, PROPERTY_VIEW_TRANSFORM, null, viewTransform); + } + + /** + * Get a copy of the view transform that is applied to the camera's layers. + */ + public PAffineTransform getViewTransform() { + return (PAffineTransform) viewTransform.clone(); + } + + /** + * Get a reference to the view transform that is applied to the camera's + * layers. + */ + public PAffineTransform getViewTransformReference() { + return viewTransform; + } + + /** + * Set the view transform that is applied to the views layers. + */ + public void setViewTransform(AffineTransform aTransform) { + viewTransform.setTransform(aTransform); + applyViewConstraints(); + invalidatePaint(); + firePropertyChange(PROPERTY_CODE_VIEW_TRANSFORM, PROPERTY_VIEW_TRANSFORM, null, viewTransform); + } + + /** + * Animate the camera's view from its current transform when the activity + * starts to a new transform that centers the given bounds in the camera + * layers coordinate system into the cameras view bounds. If the duration is + * 0 then the view will be transformed immediately, and null will be + * returned. Else a new PTransformActivity will get returned that is set to + * animate the camera's view transform to the new bounds. If shouldScale is + * true, then the camera will also scale its view so that the given bounds + * fit fully within the cameras view bounds, else the camera will maintain + * its original scale. + */ + public PTransformActivity animateViewToCenterBounds(Rectangle2D centerBounds, boolean shouldScaleToFit, + long duration) { + PBounds viewBounds = getViewBounds(); + PDimension delta = viewBounds.deltaRequiredToCenter(centerBounds); + PAffineTransform newTransform = getViewTransform(); + newTransform.translate(delta.width, delta.height); + + if (shouldScaleToFit) { + double s = Math.min(viewBounds.getWidth() / centerBounds.getWidth(), viewBounds.getHeight() + / centerBounds.getHeight()); + if (s != Double.POSITIVE_INFINITY && s != 0) { + newTransform.scaleAboutPoint(s, centerBounds.getCenterX(), centerBounds.getCenterY()); + } + } + + return animateViewToTransform(newTransform, duration); + } + + /** + * Pan the camera's view from its current transform when the activity starts + * to a new transform so that the view bounds will contain (if possible, + * intersect if not possible) the new bounds in the camera layers coordinate + * system. If the duration is 0 then the view will be transformed + * immediately, and null will be returned. Else a new PTransformActivity + * will get returned that is set to animate the camera's view transform to + * the new bounds. + */ + public PTransformActivity animateViewToPanToBounds(Rectangle2D panToBounds, long duration) { + PBounds viewBounds = getViewBounds(); + PDimension delta = viewBounds.deltaRequiredToContain(panToBounds); + + if (delta.width != 0 || delta.height != 0) { + if (duration == 0) { + translateView(-delta.width, -delta.height); + } + else { + AffineTransform at = getViewTransform(); + at.translate(-delta.width, -delta.height); + return animateViewToTransform(at, duration); + } + } + + return null; + } + + /** + * @deprecated Renamed to animateViewToPanToBounds + */ + public PTransformActivity animateViewToIncludeBounds(Rectangle2D includeBounds, long duration) { + return animateViewToPanToBounds(includeBounds, duration); + } + + /** + * Animate the cameras view transform from its current value when the + * activity starts to the new destination transform value. + */ + public PTransformActivity animateViewToTransform(AffineTransform destination, long duration) { + if (duration == 0) { + setViewTransform(destination); + return null; + } + + PTransformActivity.Target t = new PTransformActivity.Target() { + public void setTransform(AffineTransform aTransform) { + PCamera.this.setViewTransform(aTransform); + } + + public void getSourceMatrix(double[] aSource) { + PCamera.this.viewTransform.getMatrix(aSource); + } + }; + + PTransformActivity ta = new PTransformActivity(duration, PUtil.DEFAULT_ACTIVITY_STEP_RATE, t, destination); + + PRoot r = getRoot(); + if (r != null) { + r.getActivityScheduler().addActivity(ta); + } + + return ta; + } + + // **************************************************************** + // View Transform Constraints - Methods for setting and applying + // constraints to the view transform. + // **************************************************************** + + public int getViewConstraint() { + return viewConstraint; + } + + public void setViewConstraint(int constraint) { + viewConstraint = constraint; + applyViewConstraints(); + } + + protected void applyViewConstraints() { + if (viewConstraint == VIEW_CONSTRAINT_NONE) + return; + + PBounds viewBounds = getViewBounds(); + PBounds layerBounds = (PBounds) globalToLocal(getUnionOfLayerFullBounds()); + PDimension constraintDelta = null; + + switch (viewConstraint) { + case VIEW_CONSTRAINT_ALL: + constraintDelta = viewBounds.deltaRequiredToContain(layerBounds); + break; + + case VIEW_CONSTRAINT_CENTER: + layerBounds.setRect(layerBounds.getCenterX(), layerBounds.getCenterY(), 0, 0); + constraintDelta = viewBounds.deltaRequiredToContain(layerBounds); + break; + } + + viewTransform.translate(-constraintDelta.width, -constraintDelta.height); + } + + // **************************************************************** + // Camera View Coord System Conversions - Methods to translate from + // the camera's local coord system (above the camera's view transform) to + // the + // camera view coord system (below the camera's view transform). When + // converting geometry from one of the canvas's layers you must go + // through the view transform. + // **************************************************************** + + /** + * Convert the point from the camera's view coordinate system to the + * camera's local coordinate system. The given point is modified by this. + */ + public Point2D viewToLocal(Point2D viewPoint) { + return viewTransform.transform(viewPoint, viewPoint); + } + + /** + * Convert the dimension from the camera's view coordinate system to the + * camera's local coordinate system. The given dimension is modified by + * this. + */ + public Dimension2D viewToLocal(Dimension2D viewDimension) { + return viewTransform.transform(viewDimension, viewDimension); + } + + /** + * Convert the rectangle from the camera's view coordinate system to the + * camera's local coordinate system. The given rectangle is modified by this + * method. + */ + public Rectangle2D viewToLocal(Rectangle2D viewRectangle) { + return viewTransform.transform(viewRectangle, viewRectangle); + } + + /** + * Convert the point from the camera's local coordinate system to the + * camera's view coordinate system. The given point is modified by this + * method. + */ + public Point2D localToView(Point2D localPoint) { + try { + return viewTransform.inverseTransform(localPoint, localPoint); + } + catch (NoninvertibleTransformException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Convert the dimension from the camera's local coordinate system to the + * camera's view coordinate system. The given dimension is modified by this + * method. + */ + public Dimension2D localToView(Dimension2D localDimension) { + return viewTransform.inverseTransform(localDimension, localDimension); + } + + /** + * Convert the rectangle from the camera's local coordinate system to the + * camera's view coordinate system. The given rectangle is modified by this + * method. + */ + public Rectangle2D localToView(Rectangle2D localRectangle) { + return viewTransform.inverseTransform(localRectangle, localRectangle); + } + + // **************************************************************** + // Serialization - Cameras conditionally serialize their layers. + // This means that only the layer references that were unconditionally + // (using writeObject) serialized by someone else will be restored + // when the camera is unserialized. + // ****************************************************************/ + + /** + * Write this camera and all its children out to the given stream. Note that + * the cameras layers are written conditionally, so they will only get + * written out if someone else writes them unconditionally. + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + + int count = getLayerCount(); + for (int i = 0; i < count; i++) { + ((PObjectOutputStream) out).writeConditionalObject(layers.get(i)); + } + + out.writeObject(Boolean.FALSE); + ((PObjectOutputStream) out).writeConditionalObject(component); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + + layers = new ArrayList(); + + while (true) { + Object each = in.readObject(); + if (each != null) { + if (each.equals(Boolean.FALSE)) { + break; + } + else { + layers.add(each); + } + } + } + + component = (PComponent) in.readObject(); + } } - diff --git a/core/src/main/java/edu/umd/cs/piccolo/PCanvas.java b/core/src/main/java/edu/umd/cs/piccolo/PCanvas.java index cf83405..c590f03 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PCanvas.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PCanvas.java @@ -58,547 +58,595 @@ import edu.umd.cs.piccolo.util.PUtil; /** - * PCanvas is a simple Swing component that can be used to embed - * Piccolo into a Java Swing application. Canvases view the Piccolo scene graph - * through a camera. The canvas manages screen updates coming from this camera, - * and forwards swing mouse and keyboard events to the camera. + * PCanvas is a simple Swing component that can be used to embed Piccolo + * into a Java Swing application. Canvases view the Piccolo scene graph through + * a camera. The canvas manages screen updates coming from this camera, and + * forwards swing mouse and keyboard events to the camera. *
+ * * @version 1.0 * @author Jesse Grosjean */ public class PCanvas extends JComponent implements PComponent { - public static final String INTERATING_CHANGED_NOTIFICATION = "INTERATING_CHANGED_NOTIFICATION"; - - public static PCanvas CURRENT_ZCANVAS = null; + public static final String INTERATING_CHANGED_NOTIFICATION = "INTERATING_CHANGED_NOTIFICATION"; - private PCamera camera; - private PStack cursorStack; - private int interacting; - private int defaultRenderQuality; - private int animatingRenderQuality; - private int interactingRenderQuality; - private PPanEventHandler panEventHandler; - private PZoomEventHandler zoomEventHandler; - private boolean paintingImmediately; - private boolean animatingOnLastPaint; - private MouseListener mouseListener; - private KeyListener keyListener; - private MouseWheelListener mouseWheelListener; - private MouseMotionListener mouseMotionListener; - - /** - * Construct a canvas with the basic scene graph consisting of a - * root, camera, and layer. Event handlers for zooming and panning - * are automatically installed. - */ - public PCanvas() { - CURRENT_ZCANVAS = this; - cursorStack = new PStack(); - setCamera(createDefaultCamera()); - installInputSources(); - setDefaultRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING); - setAnimatingRenderQuality(PPaintContext.LOW_QUALITY_RENDERING); - setInteractingRenderQuality(PPaintContext.LOW_QUALITY_RENDERING); - setPanEventHandler(new PPanEventHandler()); - setZoomEventHandler(new PZoomEventHandler()); - setBackground(Color.WHITE); - } - - protected PCamera createDefaultCamera() { - return PUtil.createBasicScenegraph(); - } + public static PCanvas CURRENT_ZCANVAS = null; - //**************************************************************** - // Basic - Methods for accessing common piccolo nodes. - //**************************************************************** + private PCamera camera; + private PStack cursorStack; + private int interacting; + private int defaultRenderQuality; + private int animatingRenderQuality; + private int interactingRenderQuality; + private PPanEventHandler panEventHandler; + private PZoomEventHandler zoomEventHandler; + private boolean paintingImmediately; + private boolean animatingOnLastPaint; + private MouseListener mouseListener; + private KeyListener keyListener; + private MouseWheelListener mouseWheelListener; + private MouseMotionListener mouseMotionListener; - /** - * Get the pan event handler associated with this canvas. This event handler - * is set up to get events from the camera associated with this canvas by - * default. - */ - public PPanEventHandler getPanEventHandler() { - return panEventHandler; - } - - /** - * Set the pan event handler associated with this canvas. - * @param handler the new zoom event handler - */ - public void setPanEventHandler(PPanEventHandler handler) { - if(panEventHandler != null) { - removeInputEventListener(panEventHandler); - } - - panEventHandler = handler; - - if(panEventHandler != null) { - addInputEventListener(panEventHandler); - } - } - - /** - * Get the zoom event handler associated with this canvas. This event handler - * is set up to get events from the camera associated with this canvas by - * default. - */ - public PZoomEventHandler getZoomEventHandler() { - return zoomEventHandler; - } + /** + * Construct a canvas with the basic scene graph consisting of a root, + * camera, and layer. Event handlers for zooming and panning are + * automatically installed. + */ + public PCanvas() { + CURRENT_ZCANVAS = this; + cursorStack = new PStack(); + setCamera(createDefaultCamera()); + installInputSources(); + setDefaultRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING); + setAnimatingRenderQuality(PPaintContext.LOW_QUALITY_RENDERING); + setInteractingRenderQuality(PPaintContext.LOW_QUALITY_RENDERING); + setPanEventHandler(new PPanEventHandler()); + setZoomEventHandler(new PZoomEventHandler()); + setBackground(Color.WHITE); + } - /** - * Set the zoom event handler associated with this canvas. - * @param handler the new zoom event handler - */ - public void setZoomEventHandler(PZoomEventHandler handler) { - if(zoomEventHandler != null) { - removeInputEventListener(zoomEventHandler); - } - - zoomEventHandler = handler; - - if(zoomEventHandler != null) { - addInputEventListener(zoomEventHandler); - } - } - - /** - * Return the camera associated with this canvas. All input events from this canvas - * go through this camera. And this is the camera that paints this canvas. - */ - public PCamera getCamera() { - return camera; - } - - /** - * Set the camera associated with this canvas. All input events from this canvas - * go through this camera. And this is the camera that paints this canvas. - */ - public void setCamera(PCamera newCamera) { - if (camera != null) { - camera.setComponent(null); - } - - camera = newCamera; - - if (camera != null) { - camera.setComponent(this); - camera.setBounds(getBounds()); - } - } + protected PCamera createDefaultCamera() { + return PUtil.createBasicScenegraph(); + } - /** - * Return root for this canvas. - */ - public PRoot getRoot() { - return camera.getRoot(); - } - - /** - * Return layer for this canvas. - */ - public PLayer getLayer() { - return camera.getLayer(0); - } + // **************************************************************** + // Basic - Methods for accessing common piccolo nodes. + // **************************************************************** - /** - * Add an input listener to the camera associated with this canvas. - */ - public void addInputEventListener(PInputEventListener listener) { - getCamera().addInputEventListener(listener); - } - - /** - * Remove an input listener to the camera associated with this canvas. - */ - public void removeInputEventListener(PInputEventListener listener) { - getCamera().removeInputEventListener(listener); - } - - //**************************************************************** - // Painting - //**************************************************************** + /** + * Get the pan event handler associated with this canvas. This event handler + * is set up to get events from the camera associated with this canvas by + * default. + */ + public PPanEventHandler getPanEventHandler() { + return panEventHandler; + } - /** - * Return true if this canvas has been marked as interacting. If so - * the canvas will normally render at a lower quality that is faster. - */ - public boolean getInteracting() { - return interacting > 0; - } - - /** - * Return true if any activities that respond with true to the method - * isAnimating were run in the last PRoot.processInputs() loop. This - * values is used by this canvas to determine the render quality - * to use for the next paint. - */ - public boolean getAnimating() { - return getRoot().getActivityScheduler().getAnimating(); - } + /** + * Set the pan event handler associated with this canvas. + * + * @param handler the new zoom event handler + */ + public void setPanEventHandler(PPanEventHandler handler) { + if (panEventHandler != null) { + removeInputEventListener(panEventHandler); + } - /** - * Set if this canvas is interacting. If so the canvas will normally - * render at a lower quality that is faster. Also repaints the canvas if the - * render quality should change. - */ - public void setInteracting(boolean isInteracting) { - boolean wasInteracting = getInteracting(); - - if (isInteracting) { - interacting++; - } else { - interacting--; - } - - if (!getInteracting()) { // determine next render quality and repaint if it's greater then the old - // interacting render quality. - int nextRenderQuality = defaultRenderQuality; - if (getAnimating()) nextRenderQuality = animatingRenderQuality; - if (nextRenderQuality > interactingRenderQuality) { - repaint(); - } - } - - isInteracting = getInteracting(); - - if (wasInteracting != isInteracting) { - firePropertyChange(INTERATING_CHANGED_NOTIFICATION, wasInteracting, isInteracting); - } - } + panEventHandler = handler; - /** - * Set the render quality that should be used when rendering this canvas - * when it is not interacting or animating. The default value is - * PPaintContext. HIGH_QUALITY_RENDERING. - * - * @param requestedQuality supports PPaintContext.HIGH_QUALITY_RENDERING or PPaintContext.LOW_QUALITY_RENDERING - */ - public void setDefaultRenderQuality(int requestedQuality) { - defaultRenderQuality = requestedQuality; - repaint(); - } + if (panEventHandler != null) { + addInputEventListener(panEventHandler); + } + } - /** - * Set the render quality that should be used when rendering this canvas - * when it is animating. The default value is PPaintContext.LOW_QUALITY_RENDERING. - * - * @param requestedQuality supports PPaintContext.HIGH_QUALITY_RENDERING or PPaintContext.LOW_QUALITY_RENDERING - */ - public void setAnimatingRenderQuality(int requestedQuality) { - animatingRenderQuality = requestedQuality; - if (getAnimating()) repaint(); - } + /** + * Get the zoom event handler associated with this canvas. This event + * handler is set up to get events from the camera associated with this + * canvas by default. + */ + public PZoomEventHandler getZoomEventHandler() { + return zoomEventHandler; + } - /** - * Set the render quality that should be used when rendering this canvas - * when it is interacting. The default value is PPaintContext.LOW_QUALITY_RENDERING. - * - * @param requestedQuality supports PPaintContext.HIGH_QUALITY_RENDERING or PPaintContext.LOW_QUALITY_RENDERING - */ - public void setInteractingRenderQuality(int requestedQuality) { - interactingRenderQuality = requestedQuality; - if (getInteracting()) repaint(); - } - - /** - * Set the canvas cursor, and remember the previous cursor on the - * cursor stack. - */ - public void pushCursor(Cursor cursor) { - cursorStack.push(getCursor()); - setCursor(cursor); - } - - /** - * Pop the cursor on top of the cursorStack and set it as the - * canvas cursor. - */ - public void popCursor() { - setCursor((Cursor)cursorStack.pop()); - } - - //**************************************************************** - // Code to manage connection to Swing. There appears to be a bug in - // swing where it will occasionally send to many mouse pressed or mouse - // released events. Below we attempt to filter out those cases before - // they get delivered to the Piccolo framework. - //**************************************************************** - - private boolean isButton1Pressed; - private boolean isButton2Pressed; - private boolean isButton3Pressed; - - /** - * Overrride setEnabled to install/remove canvas input sources as needed. - */ - public void setEnabled(boolean enabled) { - super.setEnabled(enabled); - - if (isEnabled()) { - installInputSources(); - } else { - removeInputSources(); - } - } - - /** - * This method installs mouse and key listeners on the canvas that forward - * those events to piccolo. - */ - protected void installInputSources() { - if (mouseListener == null) { - mouseListener = new MouseListener() { - public void mouseClicked(MouseEvent e) { - sendInputEventToInputManager(e, MouseEvent.MOUSE_CLICKED); - } - - public void mouseEntered(MouseEvent e) { - MouseEvent simulated = null; - - if ((e.getModifiersEx() & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK)) != 0) { - simulated = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_DRAGGED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),e.getButton()); - } else { - simulated = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_MOVED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),e.getButton()); - } - - sendInputEventToInputManager(e, MouseEvent.MOUSE_ENTERED); - sendInputEventToInputManager(simulated, simulated.getID()); - } - - public void mouseExited(MouseEvent e) { - MouseEvent simulated = null; - - if ((e.getModifiersEx() & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK)) != 0) { - simulated = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_DRAGGED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),e.getButton()); - } else { - simulated = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_MOVED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),e.getButton()); - } - - sendInputEventToInputManager(simulated, simulated.getID()); - sendInputEventToInputManager(e, MouseEvent.MOUSE_EXITED); - } - - public void mousePressed(MouseEvent e) { - requestFocus(); - - boolean shouldBalanceEvent = false; - - if (e.getButton() == MouseEvent.NOBUTTON) { - if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK) { - e = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_PRESSED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),MouseEvent.BUTTON1); - } - else if ((e.getModifiers() & MouseEvent.BUTTON2_MASK) == MouseEvent.BUTTON2_MASK) { - e = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_PRESSED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),MouseEvent.BUTTON2); - } - else if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) == MouseEvent.BUTTON3_MASK) { - e = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_PRESSED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),MouseEvent.BUTTON3); - } - } - - switch (e.getButton()) { - case MouseEvent.BUTTON1: - if (isButton1Pressed) { - shouldBalanceEvent = true; - } - isButton1Pressed = true; - break; - - case MouseEvent.BUTTON2: - if (isButton2Pressed) { - shouldBalanceEvent = true; - } - isButton2Pressed = true; - break; - - case MouseEvent.BUTTON3: - if (isButton3Pressed) { - shouldBalanceEvent = true; - } - isButton3Pressed = true; - break; - } - - if (shouldBalanceEvent) { - MouseEvent balanceEvent = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_RELEASED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),e.getButton()); - sendInputEventToInputManager(balanceEvent, MouseEvent.MOUSE_RELEASED); - } - - sendInputEventToInputManager(e, MouseEvent.MOUSE_PRESSED); - } - - public void mouseReleased(MouseEvent e) { - boolean shouldBalanceEvent = false; - - if (e.getButton() == MouseEvent.NOBUTTON) { - if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK) { - e = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_RELEASED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),MouseEvent.BUTTON1); - } - else if ((e.getModifiers() & MouseEvent.BUTTON2_MASK) == MouseEvent.BUTTON2_MASK) { - e = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_RELEASED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),MouseEvent.BUTTON2); - } - else if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) == MouseEvent.BUTTON3_MASK) { - e = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_RELEASED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),MouseEvent.BUTTON3); - } - } - - switch (e.getButton()) { - case MouseEvent.BUTTON1: - if (!isButton1Pressed) { - shouldBalanceEvent = true; - } - isButton1Pressed = false; - break; - - case MouseEvent.BUTTON2: - if (!isButton2Pressed) { - shouldBalanceEvent = true; - } - isButton2Pressed = false; - break; - - case MouseEvent.BUTTON3: - if (!isButton3Pressed) { - shouldBalanceEvent = true; - } - isButton3Pressed = false; - break; - } - - if (shouldBalanceEvent) { - MouseEvent balanceEvent = new MouseEvent((Component)e.getSource(),MouseEvent.MOUSE_PRESSED,e.getWhen(),e.getModifiers(),e.getX(),e.getY(),e.getClickCount(),e.isPopupTrigger(),e.getButton()); - sendInputEventToInputManager(balanceEvent, MouseEvent.MOUSE_PRESSED); - } - - sendInputEventToInputManager(e, MouseEvent.MOUSE_RELEASED); - } - }; - addMouseListener(mouseListener); - } + /** + * Set the zoom event handler associated with this canvas. + * + * @param handler the new zoom event handler + */ + public void setZoomEventHandler(PZoomEventHandler handler) { + if (zoomEventHandler != null) { + removeInputEventListener(zoomEventHandler); + } - if (mouseMotionListener == null) { - mouseMotionListener = new MouseMotionListener() { - public void mouseDragged(MouseEvent e) { - sendInputEventToInputManager(e, MouseEvent.MOUSE_DRAGGED); - } - public void mouseMoved(MouseEvent e) { - sendInputEventToInputManager(e, MouseEvent.MOUSE_MOVED); - } - }; - addMouseMotionListener(mouseMotionListener); - } + zoomEventHandler = handler; - if (mouseWheelListener == null) { - mouseWheelListener = new MouseWheelListener() { - public void mouseWheelMoved(MouseWheelEvent e) { - sendInputEventToInputManager(e, e.getScrollType()); - if (!e.isConsumed() && getParent() != null) { - getParent().dispatchEvent(e); - } - } - }; - addMouseWheelListener(mouseWheelListener); - } + if (zoomEventHandler != null) { + addInputEventListener(zoomEventHandler); + } + } - if (keyListener == null) { - keyListener = new KeyListener() { - public void keyPressed(KeyEvent e) { - sendInputEventToInputManager(e, KeyEvent.KEY_PRESSED); - } - public void keyReleased(KeyEvent e) { - sendInputEventToInputManager(e, KeyEvent.KEY_RELEASED); - } - public void keyTyped(KeyEvent e) { - sendInputEventToInputManager(e, KeyEvent.KEY_TYPED); - } - }; - addKeyListener(keyListener); - } - } + /** + * Return the camera associated with this canvas. All input events from this + * canvas go through this camera. And this is the camera that paints this + * canvas. + */ + public PCamera getCamera() { + return camera; + } - /** - * This method removes mouse and key listeners on the canvas that forward - * those events to piccolo. - */ - protected void removeInputSources() { - if (mouseListener != null) removeMouseListener(mouseListener); - if (mouseMotionListener != null) removeMouseMotionListener(mouseMotionListener); - if (mouseWheelListener != null) removeMouseWheelListener(mouseWheelListener); - if (keyListener != null) removeKeyListener(keyListener); + /** + * Set the camera associated with this canvas. All input events from this + * canvas go through this camera. And this is the camera that paints this + * canvas. + */ + public void setCamera(PCamera newCamera) { + if (camera != null) { + camera.setComponent(null); + } - mouseListener = null; - mouseMotionListener = null; - mouseWheelListener = null; - keyListener = null; - } - - protected void sendInputEventToInputManager(InputEvent e, int type) { - getRoot().getDefaultInputManager().processEventFromCamera(e, type, getCamera()); - } - - public void setBounds(int x, int y, final int w, final int h) { - camera.setBounds(camera.getX(), camera.getY(), w, h); - super.setBounds(x, y, w, h); - } - - public void repaint(PBounds bounds) { - PDebug.processRepaint(); - - bounds.expandNearestIntegerDimensions(); - bounds.inset(-1, -1); - - repaint((int)bounds.x, - (int)bounds.y, - (int)bounds.width, - (int)bounds.height); - } + camera = newCamera; - public void paintComponent(Graphics g) { - PDebug.startProcessingOutput(); + if (camera != null) { + camera.setComponent(this); + camera.setBounds(getBounds()); + } + } - Graphics2D g2 = (Graphics2D) g.create(); - g2.setColor(getBackground()); - g2.fillRect(0, 0, getWidth(), getHeight()); - - // create new paint context and set render quality to lowest common - // denominator render quality. - PPaintContext paintContext = new PPaintContext(g2); - if (getInteracting() || getAnimating()) { - if (interactingRenderQuality < animatingRenderQuality) { - paintContext.setRenderQuality(interactingRenderQuality); - } else { - paintContext.setRenderQuality(animatingRenderQuality); - } - } else { - paintContext.setRenderQuality(defaultRenderQuality); - } - - // paint piccolo - camera.fullPaint(paintContext); - - // if switched state from animating to not animating invalidate the entire - // screen so that it will be drawn with the default instead of animating - // render quality. - if (!getAnimating() && animatingOnLastPaint) { - repaint(); - } - animatingOnLastPaint = getAnimating(); - - PDebug.endProcessingOutput(g2); - } - - public void paintImmediately() { - if (paintingImmediately) { - return; - } - - paintingImmediately = true; - RepaintManager.currentManager(this).paintDirtyRegions(); - paintingImmediately = false; - } + /** + * Return root for this canvas. + */ + public PRoot getRoot() { + return camera.getRoot(); + } - public Timer createTimer(int delay, ActionListener listener) { - return new Timer(delay,listener); - } + /** + * Return layer for this canvas. + */ + public PLayer getLayer() { + return camera.getLayer(0); + } + + /** + * Add an input listener to the camera associated with this canvas. + */ + public void addInputEventListener(PInputEventListener listener) { + getCamera().addInputEventListener(listener); + } + + /** + * Remove an input listener to the camera associated with this canvas. + */ + public void removeInputEventListener(PInputEventListener listener) { + getCamera().removeInputEventListener(listener); + } + + // **************************************************************** + // Painting + // **************************************************************** + + /** + * Return true if this canvas has been marked as interacting. If so the + * canvas will normally render at a lower quality that is faster. + */ + public boolean getInteracting() { + return interacting > 0; + } + + /** + * Return true if any activities that respond with true to the method + * isAnimating were run in the last PRoot.processInputs() loop. This values + * is used by this canvas to determine the render quality to use for the + * next paint. + */ + public boolean getAnimating() { + return getRoot().getActivityScheduler().getAnimating(); + } + + /** + * Set if this canvas is interacting. If so the canvas will normally render + * at a lower quality that is faster. Also repaints the canvas if the render + * quality should change. + */ + public void setInteracting(boolean isInteracting) { + boolean wasInteracting = getInteracting(); + + if (isInteracting) { + interacting++; + } + else { + interacting--; + } + + if (!getInteracting()) { // determine next render quality and repaint if + // it's greater then the old + // interacting render quality. + int nextRenderQuality = defaultRenderQuality; + if (getAnimating()) + nextRenderQuality = animatingRenderQuality; + if (nextRenderQuality > interactingRenderQuality) { + repaint(); + } + } + + isInteracting = getInteracting(); + + if (wasInteracting != isInteracting) { + firePropertyChange(INTERATING_CHANGED_NOTIFICATION, wasInteracting, isInteracting); + } + } + + /** + * Set the render quality that should be used when rendering this canvas + * when it is not interacting or animating. The default value is + * PPaintContext. HIGH_QUALITY_RENDERING. + * + * @param requestedQuality supports PPaintContext.HIGH_QUALITY_RENDERING or + * PPaintContext.LOW_QUALITY_RENDERING + */ + public void setDefaultRenderQuality(int requestedQuality) { + defaultRenderQuality = requestedQuality; + repaint(); + } + + /** + * Set the render quality that should be used when rendering this canvas + * when it is animating. The default value is + * PPaintContext.LOW_QUALITY_RENDERING. + * + * @param requestedQuality supports PPaintContext.HIGH_QUALITY_RENDERING or + * PPaintContext.LOW_QUALITY_RENDERING + */ + public void setAnimatingRenderQuality(int requestedQuality) { + animatingRenderQuality = requestedQuality; + if (getAnimating()) + repaint(); + } + + /** + * Set the render quality that should be used when rendering this canvas + * when it is interacting. The default value is + * PPaintContext.LOW_QUALITY_RENDERING. + * + * @param requestedQuality supports PPaintContext.HIGH_QUALITY_RENDERING or + * PPaintContext.LOW_QUALITY_RENDERING + */ + public void setInteractingRenderQuality(int requestedQuality) { + interactingRenderQuality = requestedQuality; + if (getInteracting()) + repaint(); + } + + /** + * Set the canvas cursor, and remember the previous cursor on the cursor + * stack. + */ + public void pushCursor(Cursor cursor) { + cursorStack.push(getCursor()); + setCursor(cursor); + } + + /** + * Pop the cursor on top of the cursorStack and set it as the canvas cursor. + */ + public void popCursor() { + setCursor((Cursor) cursorStack.pop()); + } + + // **************************************************************** + // Code to manage connection to Swing. There appears to be a bug in + // swing where it will occasionally send to many mouse pressed or mouse + // released events. Below we attempt to filter out those cases before + // they get delivered to the Piccolo framework. + // **************************************************************** + + private boolean isButton1Pressed; + private boolean isButton2Pressed; + private boolean isButton3Pressed; + + /** + * Overrride setEnabled to install/remove canvas input sources as needed. + */ + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + + if (isEnabled()) { + installInputSources(); + } + else { + removeInputSources(); + } + } + + /** + * This method installs mouse and key listeners on the canvas that forward + * those events to piccolo. + */ + protected void installInputSources() { + if (mouseListener == null) { + mouseListener = new MouseListener() { + public void mouseClicked(MouseEvent e) { + sendInputEventToInputManager(e, MouseEvent.MOUSE_CLICKED); + } + + public void mouseEntered(MouseEvent e) { + MouseEvent simulated = null; + + if ((e.getModifiersEx() & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK)) != 0) { + simulated = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_DRAGGED, e.getWhen(), e + .getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), e + .getButton()); + } + else { + simulated = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_MOVED, e.getWhen(), e + .getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), e + .getButton()); + } + + sendInputEventToInputManager(e, MouseEvent.MOUSE_ENTERED); + sendInputEventToInputManager(simulated, simulated.getID()); + } + + public void mouseExited(MouseEvent e) { + MouseEvent simulated = null; + + if ((e.getModifiersEx() & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK)) != 0) { + simulated = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_DRAGGED, e.getWhen(), e + .getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), e + .getButton()); + } + else { + simulated = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_MOVED, e.getWhen(), e + .getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), e + .getButton()); + } + + sendInputEventToInputManager(simulated, simulated.getID()); + sendInputEventToInputManager(e, MouseEvent.MOUSE_EXITED); + } + + public void mousePressed(MouseEvent e) { + requestFocus(); + + boolean shouldBalanceEvent = false; + + if (e.getButton() == MouseEvent.NOBUTTON) { + if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK) { + e = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_PRESSED, e.getWhen(), e + .getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), + MouseEvent.BUTTON1); + } + else if ((e.getModifiers() & MouseEvent.BUTTON2_MASK) == MouseEvent.BUTTON2_MASK) { + e = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_PRESSED, e.getWhen(), e + .getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), + MouseEvent.BUTTON2); + } + else if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) == MouseEvent.BUTTON3_MASK) { + e = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_PRESSED, e.getWhen(), e + .getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), + MouseEvent.BUTTON3); + } + } + + switch (e.getButton()) { + case MouseEvent.BUTTON1: + if (isButton1Pressed) { + shouldBalanceEvent = true; + } + isButton1Pressed = true; + break; + + case MouseEvent.BUTTON2: + if (isButton2Pressed) { + shouldBalanceEvent = true; + } + isButton2Pressed = true; + break; + + case MouseEvent.BUTTON3: + if (isButton3Pressed) { + shouldBalanceEvent = true; + } + isButton3Pressed = true; + break; + } + + if (shouldBalanceEvent) { + MouseEvent balanceEvent = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_RELEASED, + e.getWhen(), e.getModifiers(), e.getX(), e.getY(), e.getClickCount(), e + .isPopupTrigger(), e.getButton()); + sendInputEventToInputManager(balanceEvent, MouseEvent.MOUSE_RELEASED); + } + + sendInputEventToInputManager(e, MouseEvent.MOUSE_PRESSED); + } + + public void mouseReleased(MouseEvent e) { + boolean shouldBalanceEvent = false; + + if (e.getButton() == MouseEvent.NOBUTTON) { + if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == MouseEvent.BUTTON1_MASK) { + e = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_RELEASED, e.getWhen(), e + .getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), + MouseEvent.BUTTON1); + } + else if ((e.getModifiers() & MouseEvent.BUTTON2_MASK) == MouseEvent.BUTTON2_MASK) { + e = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_RELEASED, e.getWhen(), e + .getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), + MouseEvent.BUTTON2); + } + else if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) == MouseEvent.BUTTON3_MASK) { + e = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_RELEASED, e.getWhen(), e + .getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), + MouseEvent.BUTTON3); + } + } + + switch (e.getButton()) { + case MouseEvent.BUTTON1: + if (!isButton1Pressed) { + shouldBalanceEvent = true; + } + isButton1Pressed = false; + break; + + case MouseEvent.BUTTON2: + if (!isButton2Pressed) { + shouldBalanceEvent = true; + } + isButton2Pressed = false; + break; + + case MouseEvent.BUTTON3: + if (!isButton3Pressed) { + shouldBalanceEvent = true; + } + isButton3Pressed = false; + break; + } + + if (shouldBalanceEvent) { + MouseEvent balanceEvent = new MouseEvent((Component) e.getSource(), MouseEvent.MOUSE_PRESSED, e + .getWhen(), e.getModifiers(), e.getX(), e.getY(), e.getClickCount(), + e.isPopupTrigger(), e.getButton()); + sendInputEventToInputManager(balanceEvent, MouseEvent.MOUSE_PRESSED); + } + + sendInputEventToInputManager(e, MouseEvent.MOUSE_RELEASED); + } + }; + addMouseListener(mouseListener); + } + + if (mouseMotionListener == null) { + mouseMotionListener = new MouseMotionListener() { + public void mouseDragged(MouseEvent e) { + sendInputEventToInputManager(e, MouseEvent.MOUSE_DRAGGED); + } + + public void mouseMoved(MouseEvent e) { + sendInputEventToInputManager(e, MouseEvent.MOUSE_MOVED); + } + }; + addMouseMotionListener(mouseMotionListener); + } + + if (mouseWheelListener == null) { + mouseWheelListener = new MouseWheelListener() { + public void mouseWheelMoved(MouseWheelEvent e) { + sendInputEventToInputManager(e, e.getScrollType()); + if (!e.isConsumed() && getParent() != null) { + getParent().dispatchEvent(e); + } + } + }; + addMouseWheelListener(mouseWheelListener); + } + + if (keyListener == null) { + keyListener = new KeyListener() { + public void keyPressed(KeyEvent e) { + sendInputEventToInputManager(e, KeyEvent.KEY_PRESSED); + } + + public void keyReleased(KeyEvent e) { + sendInputEventToInputManager(e, KeyEvent.KEY_RELEASED); + } + + public void keyTyped(KeyEvent e) { + sendInputEventToInputManager(e, KeyEvent.KEY_TYPED); + } + }; + addKeyListener(keyListener); + } + } + + /** + * This method removes mouse and key listeners on the canvas that forward + * those events to piccolo. + */ + protected void removeInputSources() { + if (mouseListener != null) + removeMouseListener(mouseListener); + if (mouseMotionListener != null) + removeMouseMotionListener(mouseMotionListener); + if (mouseWheelListener != null) + removeMouseWheelListener(mouseWheelListener); + if (keyListener != null) + removeKeyListener(keyListener); + + mouseListener = null; + mouseMotionListener = null; + mouseWheelListener = null; + keyListener = null; + } + + protected void sendInputEventToInputManager(InputEvent e, int type) { + getRoot().getDefaultInputManager().processEventFromCamera(e, type, getCamera()); + } + + public void setBounds(int x, int y, final int w, final int h) { + camera.setBounds(camera.getX(), camera.getY(), w, h); + super.setBounds(x, y, w, h); + } + + public void repaint(PBounds bounds) { + PDebug.processRepaint(); + + bounds.expandNearestIntegerDimensions(); + bounds.inset(-1, -1); + + repaint((int) bounds.x, (int) bounds.y, (int) bounds.width, (int) bounds.height); + } + + public void paintComponent(Graphics g) { + PDebug.startProcessingOutput(); + + Graphics2D g2 = (Graphics2D) g.create(); + g2.setColor(getBackground()); + g2.fillRect(0, 0, getWidth(), getHeight()); + + // create new paint context and set render quality to lowest common + // denominator render quality. + PPaintContext paintContext = new PPaintContext(g2); + if (getInteracting() || getAnimating()) { + if (interactingRenderQuality < animatingRenderQuality) { + paintContext.setRenderQuality(interactingRenderQuality); + } + else { + paintContext.setRenderQuality(animatingRenderQuality); + } + } + else { + paintContext.setRenderQuality(defaultRenderQuality); + } + + // paint piccolo + camera.fullPaint(paintContext); + + // if switched state from animating to not animating invalidate the + // entire + // screen so that it will be drawn with the default instead of animating + // render quality. + if (!getAnimating() && animatingOnLastPaint) { + repaint(); + } + animatingOnLastPaint = getAnimating(); + + PDebug.endProcessingOutput(g2); + } + + public void paintImmediately() { + if (paintingImmediately) { + return; + } + + paintingImmediately = true; + RepaintManager.currentManager(this).paintDirtyRegions(); + paintingImmediately = false; + } + + public Timer createTimer(int delay, ActionListener listener) { + return new Timer(delay, listener); + } } \ No newline at end of file diff --git a/core/src/main/java/edu/umd/cs/piccolo/PComponent.java b/core/src/main/java/edu/umd/cs/piccolo/PComponent.java index 45a1780..45817eb 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PComponent.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PComponent.java @@ -41,14 +41,14 @@ * @author Lance Good */ public interface PComponent { - - public void repaint(PBounds bounds); - - public void paintImmediately(); - - public void pushCursor(Cursor cursor); - - public void popCursor(); - public void setInteracting(boolean interacting); + public void repaint(PBounds bounds); + + public void paintImmediately(); + + public void pushCursor(Cursor cursor); + + public void popCursor(); + + public void setInteracting(boolean interacting); } diff --git a/core/src/main/java/edu/umd/cs/piccolo/PInputManager.java b/core/src/main/java/edu/umd/cs/piccolo/PInputManager.java index b810cb6..dcb2a8d 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PInputManager.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PInputManager.java @@ -42,10 +42,10 @@ import edu.umd.cs.piccolo.util.PPickPath; /** - * PInputManager is responsible for dispatching PInputEvents - * to node's event listeners. Events are dispatched from PRoot's processInputs - * method. + * PInputManager is responsible for dispatching PInputEvents to node's + * event listeners. Events are dispatched from PRoot's processInputs method. *
+ * * @see edu.umd.cs.piccolo.event.PInputEvent * @see PRoot * @version 1.0 @@ -53,227 +53,232 @@ */ public class PInputManager extends PBasicInputEventHandler implements PRoot.InputSource { - private Point2D lastCanvasPosition; - private Point2D currentCanvasPosition; + private Point2D lastCanvasPosition; + private Point2D currentCanvasPosition; - private InputEvent nextInput; - private int nextType; - private PCamera nextInputSource; + private InputEvent nextInput; + private int nextType; + private PCamera nextInputSource; - private PPickPath mouseFocus; - private PPickPath previousMouseFocus; - private PPickPath mouseOver; - private PPickPath previousMouseOver; - private PInputEventListener keyboardFocus; + private PPickPath mouseFocus; + private PPickPath previousMouseFocus; + private PPickPath mouseOver; + private PPickPath previousMouseOver; + private PInputEventListener keyboardFocus; - private int pressedCount; - - public PInputManager() { - super(); - lastCanvasPosition = new Point2D.Double(); - currentCanvasPosition = new Point2D.Double(); - } - - //**************************************************************** - // Basic - //**************************************************************** - - /** - * Return the node that currently has the keyboard focus. This node - * receives the key events. - */ - public PInputEventListener getKeyboardFocus() { - return keyboardFocus; - } - - /** - * Set the node that should recive key events. - */ - public void setKeyboardFocus(PInputEventListener eventHandler) { - PInputEvent focusEvent = new PInputEvent(this, null); - - if (keyboardFocus != null) { - dispatchEventToListener(focusEvent, FocusEvent.FOCUS_LOST, keyboardFocus); - } - - keyboardFocus = eventHandler; - - if (keyboardFocus != null) { - dispatchEventToListener(focusEvent, FocusEvent.FOCUS_GAINED, keyboardFocus); - } - } - - /** - * Return the node that currently has the mouse focus. This will return - * the node that received the current mouse pressed event, or null if the - * mouse is not pressed. The mouse focus gets mouse dragged events even - * what the mouse is not over the mouse focus. - */ - public PPickPath getMouseFocus() { - return mouseFocus; - } - - public void setMouseFocus(PPickPath path) { - previousMouseFocus = mouseFocus; - mouseFocus = path; - } + private int pressedCount; - /** - * Return the node the the mouse is currently over. - */ - public PPickPath getMouseOver() { - return mouseOver; - } - - public void setMouseOver(PPickPath path) { - mouseOver = path; - } - - public Point2D getLastCanvasPosition() { - return lastCanvasPosition; - } + public PInputManager() { + super(); + lastCanvasPosition = new Point2D.Double(); + currentCanvasPosition = new Point2D.Double(); + } - public Point2D getCurrentCanvasPosition() { - return currentCanvasPosition; - } - - //**************************************************************** - // Event Handling - Methods for handling events - // - // The dispatch manager updates the focus nodes based on the - // incoming events, and dispatches those events to the appropriate - // focus nodes. - //**************************************************************** - - public void keyPressed(PInputEvent event) { - dispatchEventToListener(event, KeyEvent.KEY_PRESSED, keyboardFocus); - } - - public void keyReleased(PInputEvent event) { - dispatchEventToListener(event, KeyEvent.KEY_RELEASED, keyboardFocus); - } - - public void keyTyped(PInputEvent event) { - dispatchEventToListener(event, KeyEvent.KEY_TYPED, keyboardFocus); - } - - public void mouseClicked(PInputEvent event) { - dispatchEventToListener(event, MouseEvent.MOUSE_CLICKED, previousMouseFocus); - } - - public void mouseWheelRotated(PInputEvent event) { - setMouseFocus(getMouseOver()); - dispatchEventToListener(event, MouseWheelEvent.WHEEL_UNIT_SCROLL, mouseOver); - } - - public void mouseWheelRotatedByBlock(PInputEvent event) { - setMouseFocus(getMouseOver()); - dispatchEventToListener(event, MouseWheelEvent.WHEEL_BLOCK_SCROLL, mouseOver); - } - - public void mouseDragged(PInputEvent event) { - checkForMouseEnteredAndExited(event); - dispatchEventToListener(event, MouseEvent.MOUSE_DRAGGED, mouseFocus); - } - - public void mouseEntered(PInputEvent event) { - dispatchEventToListener(event, MouseEvent.MOUSE_ENTERED, mouseOver); - } - - public void mouseExited(PInputEvent event) { - dispatchEventToListener(event, MouseEvent.MOUSE_EXITED, previousMouseOver); - } - - public void mouseMoved(PInputEvent event) { - checkForMouseEnteredAndExited(event); - dispatchEventToListener(event, MouseEvent.MOUSE_MOVED, mouseOver); - } - - public void mousePressed(PInputEvent event) { - if (pressedCount == 0) { - setMouseFocus(getMouseOver()); - } - pressedCount++; - dispatchEventToListener(event, MouseEvent.MOUSE_PRESSED, mouseFocus); - if (pressedCount < 1 || pressedCount > 3) System.err.println("invalid pressedCount on mouse pressed: " + pressedCount); - } - - public void mouseReleased(PInputEvent event) { - pressedCount--; - checkForMouseEnteredAndExited(event); - dispatchEventToListener(event, MouseEvent.MOUSE_RELEASED, mouseFocus); - if (pressedCount == 0) { - setMouseFocus(null); - } - if (pressedCount < 0 || pressedCount > 2) System.err.println("invalid pressedCount on mouse released: " + pressedCount); - } - - protected void checkForMouseEnteredAndExited(PInputEvent event) { - PNode c = (mouseOver != null) ? mouseOver.getPickedNode() : null; - PNode p = (previousMouseOver != null) ? previousMouseOver.getPickedNode() : null; - - if (c != p) { - dispatchEventToListener(event, MouseEvent.MOUSE_EXITED, previousMouseOver); - dispatchEventToListener(event, MouseEvent.MOUSE_ENTERED, mouseOver); - previousMouseOver = mouseOver; - } - } - - //**************************************************************** - // Event Dispatch. - //**************************************************************** + // **************************************************************** + // Basic + // **************************************************************** - public void processInput() { - if (nextInput == null) return; + /** + * Return the node that currently has the keyboard focus. This node receives + * the key events. + */ + public PInputEventListener getKeyboardFocus() { + return keyboardFocus; + } - PInputEvent e = new PInputEvent(this, nextInput); - - Point2D newCurrentCanvasPosition = null; - Point2D newLastCanvasPosition = null; - - if (e.isMouseEvent()) { - if (e.isMouseEnteredOrMouseExited()) { - PPickPath aPickPath = nextInputSource.pick(((MouseEvent)nextInput).getX(), ((MouseEvent)nextInput).getY(), 1); - setMouseOver(aPickPath); - previousMouseOver = aPickPath; - newCurrentCanvasPosition = (Point2D) currentCanvasPosition.clone(); - newLastCanvasPosition = (Point2D) lastCanvasPosition.clone(); - } else { - lastCanvasPosition.setLocation(currentCanvasPosition); - currentCanvasPosition.setLocation(((MouseEvent)nextInput).getX(), ((MouseEvent)nextInput).getY()); - PPickPath aPickPath = nextInputSource.pick(currentCanvasPosition.getX(), currentCanvasPosition.getY(), 1); - setMouseOver(aPickPath); - } - } - - nextInput = null; - nextInputSource = null; - - this.processEvent(e, nextType); - - if (newCurrentCanvasPosition != null && newLastCanvasPosition != null) { - currentCanvasPosition.setLocation(newCurrentCanvasPosition); - lastCanvasPosition.setLocation(newLastCanvasPosition); - } - } + /** + * Set the node that should recive key events. + */ + public void setKeyboardFocus(PInputEventListener eventHandler) { + PInputEvent focusEvent = new PInputEvent(this, null); - public void processEventFromCamera(InputEvent event, int type, PCamera camera) { - // queue input - nextInput = event; - nextType = type; - nextInputSource = camera; - - // tell root to process queued inputs - camera.getRoot().processInputs(); - } - - private void dispatchEventToListener(PInputEvent event, int type, PInputEventListener listener) { - if (listener != null) { - // clear the handled bit since the same event object is used to send multiple events such as - // mouseEntered/mouseExited and mouseMove. - event.setHandled(false); - listener.processEvent(event, type); - } - } + if (keyboardFocus != null) { + dispatchEventToListener(focusEvent, FocusEvent.FOCUS_LOST, keyboardFocus); + } + + keyboardFocus = eventHandler; + + if (keyboardFocus != null) { + dispatchEventToListener(focusEvent, FocusEvent.FOCUS_GAINED, keyboardFocus); + } + } + + /** + * Return the node that currently has the mouse focus. This will return the + * node that received the current mouse pressed event, or null if the mouse + * is not pressed. The mouse focus gets mouse dragged events even what the + * mouse is not over the mouse focus. + */ + public PPickPath getMouseFocus() { + return mouseFocus; + } + + public void setMouseFocus(PPickPath path) { + previousMouseFocus = mouseFocus; + mouseFocus = path; + } + + /** + * Return the node the the mouse is currently over. + */ + public PPickPath getMouseOver() { + return mouseOver; + } + + public void setMouseOver(PPickPath path) { + mouseOver = path; + } + + public Point2D getLastCanvasPosition() { + return lastCanvasPosition; + } + + public Point2D getCurrentCanvasPosition() { + return currentCanvasPosition; + } + + // **************************************************************** + // Event Handling - Methods for handling events + // + // The dispatch manager updates the focus nodes based on the + // incoming events, and dispatches those events to the appropriate + // focus nodes. + // **************************************************************** + + public void keyPressed(PInputEvent event) { + dispatchEventToListener(event, KeyEvent.KEY_PRESSED, keyboardFocus); + } + + public void keyReleased(PInputEvent event) { + dispatchEventToListener(event, KeyEvent.KEY_RELEASED, keyboardFocus); + } + + public void keyTyped(PInputEvent event) { + dispatchEventToListener(event, KeyEvent.KEY_TYPED, keyboardFocus); + } + + public void mouseClicked(PInputEvent event) { + dispatchEventToListener(event, MouseEvent.MOUSE_CLICKED, previousMouseFocus); + } + + public void mouseWheelRotated(PInputEvent event) { + setMouseFocus(getMouseOver()); + dispatchEventToListener(event, MouseWheelEvent.WHEEL_UNIT_SCROLL, mouseOver); + } + + public void mouseWheelRotatedByBlock(PInputEvent event) { + setMouseFocus(getMouseOver()); + dispatchEventToListener(event, MouseWheelEvent.WHEEL_BLOCK_SCROLL, mouseOver); + } + + public void mouseDragged(PInputEvent event) { + checkForMouseEnteredAndExited(event); + dispatchEventToListener(event, MouseEvent.MOUSE_DRAGGED, mouseFocus); + } + + public void mouseEntered(PInputEvent event) { + dispatchEventToListener(event, MouseEvent.MOUSE_ENTERED, mouseOver); + } + + public void mouseExited(PInputEvent event) { + dispatchEventToListener(event, MouseEvent.MOUSE_EXITED, previousMouseOver); + } + + public void mouseMoved(PInputEvent event) { + checkForMouseEnteredAndExited(event); + dispatchEventToListener(event, MouseEvent.MOUSE_MOVED, mouseOver); + } + + public void mousePressed(PInputEvent event) { + if (pressedCount == 0) { + setMouseFocus(getMouseOver()); + } + pressedCount++; + dispatchEventToListener(event, MouseEvent.MOUSE_PRESSED, mouseFocus); + if (pressedCount < 1 || pressedCount > 3) + System.err.println("invalid pressedCount on mouse pressed: " + pressedCount); + } + + public void mouseReleased(PInputEvent event) { + pressedCount--; + checkForMouseEnteredAndExited(event); + dispatchEventToListener(event, MouseEvent.MOUSE_RELEASED, mouseFocus); + if (pressedCount == 0) { + setMouseFocus(null); + } + if (pressedCount < 0 || pressedCount > 2) + System.err.println("invalid pressedCount on mouse released: " + pressedCount); + } + + protected void checkForMouseEnteredAndExited(PInputEvent event) { + PNode c = (mouseOver != null) ? mouseOver.getPickedNode() : null; + PNode p = (previousMouseOver != null) ? previousMouseOver.getPickedNode() : null; + + if (c != p) { + dispatchEventToListener(event, MouseEvent.MOUSE_EXITED, previousMouseOver); + dispatchEventToListener(event, MouseEvent.MOUSE_ENTERED, mouseOver); + previousMouseOver = mouseOver; + } + } + + // **************************************************************** + // Event Dispatch. + // **************************************************************** + + public void processInput() { + if (nextInput == null) + return; + + PInputEvent e = new PInputEvent(this, nextInput); + + Point2D newCurrentCanvasPosition = null; + Point2D newLastCanvasPosition = null; + + if (e.isMouseEvent()) { + if (e.isMouseEnteredOrMouseExited()) { + PPickPath aPickPath = nextInputSource.pick(((MouseEvent) nextInput).getX(), ((MouseEvent) nextInput) + .getY(), 1); + setMouseOver(aPickPath); + previousMouseOver = aPickPath; + newCurrentCanvasPosition = (Point2D) currentCanvasPosition.clone(); + newLastCanvasPosition = (Point2D) lastCanvasPosition.clone(); + } + else { + lastCanvasPosition.setLocation(currentCanvasPosition); + currentCanvasPosition.setLocation(((MouseEvent) nextInput).getX(), ((MouseEvent) nextInput).getY()); + PPickPath aPickPath = nextInputSource.pick(currentCanvasPosition.getX(), currentCanvasPosition.getY(), + 1); + setMouseOver(aPickPath); + } + } + + nextInput = null; + nextInputSource = null; + + this.processEvent(e, nextType); + + if (newCurrentCanvasPosition != null && newLastCanvasPosition != null) { + currentCanvasPosition.setLocation(newCurrentCanvasPosition); + lastCanvasPosition.setLocation(newLastCanvasPosition); + } + } + + public void processEventFromCamera(InputEvent event, int type, PCamera camera) { + // queue input + nextInput = event; + nextType = type; + nextInputSource = camera; + + // tell root to process queued inputs + camera.getRoot().processInputs(); + } + + private void dispatchEventToListener(PInputEvent event, int type, PInputEventListener listener) { + if (listener != null) { + // clear the handled bit since the same event object is used to send + // multiple events such as mouseEntered/mouseExited and mouseMove. + event.setHandled(false); + listener.processEvent(event, type); + } + } } - diff --git a/core/src/main/java/edu/umd/cs/piccolo/PLayer.java b/core/src/main/java/edu/umd/cs/piccolo/PLayer.java index 5a9aed9..6f53c70 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PLayer.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PLayer.java @@ -40,13 +40,14 @@ /** * PLayer is a node that can be viewed directly by multiple camera nodes. - * Generally child nodes are added to a layer to give the viewing cameras + * Generally child nodes are added to a layer to give the viewing cameras * something to look at. *
- * A single layer node may be viewed through multiple cameras with each - * camera using its own view transform. This means that any node (since layers can have + * A single layer node may be viewed through multiple cameras with each camera + * using its own view transform. This means that any node (since layers can have * children) may be visible through multiple cameras at the same time. *
+ * * @see PCamera * @see edu.umd.cs.piccolo.event.PInputEvent * @see edu.umd.cs.piccolo.util.PPickPath @@ -54,155 +55,157 @@ * @author Jesse Grosjean */ public class PLayer extends PNode { - - /** - * The property name that identifies a change in the set of this layer's - * cameras (see {@link #getCamera getCamera}, {@link #getCameraCount - * getCameraCount}, {@link #getCamerasReference getCamerasReference}). In - * any property change event the new value will be a reference to the list - * of cameras, but old value will always be null. - */ - public static final String PROPERTY_CAMERAS = "cameras"; + + /** + * The property name that identifies a change in the set of this layer's + * cameras (see {@link #getCamera getCamera}, {@link #getCameraCount + * getCameraCount}, {@link #getCamerasReference getCamerasReference}). In + * any property change event the new value will be a reference to the list + * of cameras, but old value will always be null. + */ + public static final String PROPERTY_CAMERAS = "cameras"; public static final int PROPERTY_CODE_CAMERAS = 1 << 13; - private transient List cameras; + private transient List cameras; - public PLayer() { - super(); - cameras = new ArrayList(); - } - - //**************************************************************** - // Cameras - Maintain the list of cameras that are viewing this - // layer. - //**************************************************************** - - /** - * Get the list of cameras viewing this layer. - */ - public List getCamerasReference() { - return cameras; - } + public PLayer() { + super(); + cameras = new ArrayList(); + } - /** - * Get the number of cameras viewing this layer. - */ - public int getCameraCount() { - if (cameras == null) { - return 0; - } - return cameras.size(); - } - - /** - * Get the camera in this layer's camera list at the specified index. - */ - public PCamera getCamera(int index) { - return (PCamera) cameras.get(index); - } - - /** - * Add a camera to this layer's camera list. This method it called automatically - * when a layer is added to a camera. - */ - public void addCamera(PCamera camera) { - addCamera(cameras.size(), camera); - } - - /** - * Add a camera to this layer's camera list at the specified index. This - * method it called automatically when a layer is added to a camera. - */ - public void addCamera(int index, PCamera camera) { - cameras.add(index, camera); - invalidatePaint(); - firePropertyChange(PROPERTY_CODE_CAMERAS, PROPERTY_CAMERAS, null, cameras); - } + // **************************************************************** + // Cameras - Maintain the list of cameras that are viewing this + // layer. + // **************************************************************** - /** - * Remove the camera from this layer's camera list. - */ - public PCamera removeCamera(PCamera camera) { - return removeCamera(cameras.indexOf(camera)); - } - - /** - * Remove the camera at the given index from this layer's camera list. - */ - public PCamera removeCamera(int index) { - PCamera result = (PCamera) cameras.remove(index); - invalidatePaint(); - firePropertyChange(PROPERTY_CODE_CAMERAS, PROPERTY_CAMERAS, null, cameras); - return result; - } - - //**************************************************************** - // Camera Repaint Notifications - Layer nodes must forward their - // repaints to each camera that is viewing them so that the camera - // views will also get repainted. - //**************************************************************** - - /** - * Override repaints and forward them to the cameras that are - * viewing this layer. - */ - public void repaintFrom(PBounds localBounds, PNode childOrThis) { - if (childOrThis != this) { - localToParent(localBounds); - } - - notifyCameras(localBounds); - - if (getParent() != null) { - getParent().repaintFrom(localBounds, childOrThis); - } - } - - protected void notifyCameras(PBounds parentBounds) { - int count = getCameraCount(); - for (int i = 0; i < count; i++) { - PCamera each = (PCamera) cameras.get(i); - each.repaintFromLayer(parentBounds, this); - } - } - - //**************************************************************** - // Serialization - Layers conditionally serialize their cameras. - // This means that only the camera references that were unconditionally - // (using writeObject) serialized by someone else will be restored - // when the layer is unserialized. - //**************************************************************** - - /** - * Write this layer and all its children out to the given stream. Note - * that the layer writes out any cameras that are viewing it conditionally, so they will only - * get written out if someone else writes them unconditionally. - */ - private void writeObject(ObjectOutputStream out) throws IOException { - out.defaultWriteObject(); - - int count = getCameraCount(); - for (int i = 0; i < count; i++) { - ((PObjectOutputStream)out).writeConditionalObject(cameras.get(i)); - } - - out.writeObject(Boolean.FALSE); - } + /** + * Get the list of cameras viewing this layer. + */ + public List getCamerasReference() { + return cameras; + } - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - - cameras = new ArrayList(); - - while (true) { - Object each = in.readObject(); - if (each != null) { - if (each.equals(Boolean.FALSE)) { - break; - } else { - cameras.add(each); - } - } - } - } + /** + * Get the number of cameras viewing this layer. + */ + public int getCameraCount() { + if (cameras == null) { + return 0; + } + return cameras.size(); + } + + /** + * Get the camera in this layer's camera list at the specified index. + */ + public PCamera getCamera(int index) { + return (PCamera) cameras.get(index); + } + + /** + * Add a camera to this layer's camera list. This method it called + * automatically when a layer is added to a camera. + */ + public void addCamera(PCamera camera) { + addCamera(cameras.size(), camera); + } + + /** + * Add a camera to this layer's camera list at the specified index. This + * method it called automatically when a layer is added to a camera. + */ + public void addCamera(int index, PCamera camera) { + cameras.add(index, camera); + invalidatePaint(); + firePropertyChange(PROPERTY_CODE_CAMERAS, PROPERTY_CAMERAS, null, cameras); + } + + /** + * Remove the camera from this layer's camera list. + */ + public PCamera removeCamera(PCamera camera) { + return removeCamera(cameras.indexOf(camera)); + } + + /** + * Remove the camera at the given index from this layer's camera list. + */ + public PCamera removeCamera(int index) { + PCamera result = (PCamera) cameras.remove(index); + invalidatePaint(); + firePropertyChange(PROPERTY_CODE_CAMERAS, PROPERTY_CAMERAS, null, cameras); + return result; + } + + // **************************************************************** + // Camera Repaint Notifications - Layer nodes must forward their + // repaints to each camera that is viewing them so that the camera + // views will also get repainted. + // **************************************************************** + + /** + * Override repaints and forward them to the cameras that are viewing this + * layer. + */ + public void repaintFrom(PBounds localBounds, PNode childOrThis) { + if (childOrThis != this) { + localToParent(localBounds); + } + + notifyCameras(localBounds); + + if (getParent() != null) { + getParent().repaintFrom(localBounds, childOrThis); + } + } + + protected void notifyCameras(PBounds parentBounds) { + int count = getCameraCount(); + for (int i = 0; i < count; i++) { + PCamera each = (PCamera) cameras.get(i); + each.repaintFromLayer(parentBounds, this); + } + } + + // **************************************************************** + // Serialization - Layers conditionally serialize their cameras. + // This means that only the camera references that were unconditionally + // (using writeObject) serialized by someone else will be restored + // when the layer is unserialized. + // **************************************************************** + + /** + * Write this layer and all its children out to the given stream. Note that + * the layer writes out any cameras that are viewing it conditionally, so + * they will only get written out if someone else writes them + * unconditionally. + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + + int count = getCameraCount(); + for (int i = 0; i < count; i++) { + ((PObjectOutputStream) out).writeConditionalObject(cameras.get(i)); + } + + out.writeObject(Boolean.FALSE); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + + cameras = new ArrayList(); + + while (true) { + Object each = in.readObject(); + if (each != null) { + if (each.equals(Boolean.FALSE)) { + break; + } + else { + cameras.add(each); + } + } + } + } } diff --git a/core/src/main/java/edu/umd/cs/piccolo/PNode.java b/core/src/main/java/edu/umd/cs/piccolo/PNode.java index 145bac8..40d2e07 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PNode.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PNode.java @@ -84,411 +84,423 @@ /** * PNode is the central abstraction in Piccolo. All objects that are - * visible on the screen are instances of the node class. All nodes may have + * visible on the screen are instances of the node class. All nodes may have * other "child" nodes added to them. *
* See edu.umd.piccolo.examples.NodeExample.java for demonstrations of how nodes * can be used and how new types of nodes can be created. *
+ * * @version 1.0 * @author Jesse Grosjean */ public class PNode implements Cloneable, Serializable, Printable { - - /** - * The property name that identifies a change in this node's client - * propertie (see {@link #getClientProperty getClientProperty}). In - * an property change event the new value will be a reference to the map of - * client properties but old value will always be null. - */ - public static final String PROPERTY_CLIENT_PROPERTIES = "clientProperties"; - public static final int PROPERTY_CODE_CLIENT_PROPERTIES = 1 << 0; - - /** - * The property name that identifies a change of this node's bounds (see - * {@link #getBounds getBounds}, {@link #getBoundsReference - * getBoundsReference}). In any property change event the new value will be - * a reference to this node's bounds, but old value will always be null. - */ - public static final String PROPERTY_BOUNDS = "bounds"; - public static final int PROPERTY_CODE_BOUNDS = 1 << 1; - - /** - * The property name that identifies a change of this node's full bounds - * (see {@link #getFullBounds getFullBounds}, {@link #getFullBoundsReference - * getFullBoundsReference}). In any property change event the new value will - * be a reference to this node's full bounds cache, but old value will - * always be null. - */ - public static final String PROPERTY_FULL_BOUNDS = "fullBounds"; - public static final int PROPERTY_CODE_FULL_BOUNDS = 1 << 2; - - /** - * The property name that identifies a change of this node's transform (see - * {@link #getTransform getTransform}, {@link #getTransformReference - * getTransformReference}). In any property change event the new value will - * be a reference to this node's transform, but old value will always be - * null. - */ - public static final String PROPERTY_TRANSFORM = "transform"; - public static final int PROPERTY_CODE_TRANSFORM = 1 << 3; - - /** - * The property name that identifies a change of this node's visibility (see - * {@link #getVisible getVisible}). Both old value and new value will be - * null in any property change event. - */ - public static final String PROPERTY_VISIBLE = "visible"; - public static final int PROPERTY_CODE_VISIBLE = 1 << 4; - - /** - * The property name that identifies a change of this node's paint (see - * {@link #getPaint getPaint}). Both old value and new value will be set - * correctly in any property change event. - */ - public static final String PROPERTY_PAINT = "paint"; - public static final int PROPERTY_CODE_PAINT = 1 << 5; - - /** - * The property name that identifies a change of this node's transparency - * (see {@link #getTransparency getTransparency}). Both old value and new - * value will be null in any property change event. - */ - public static final String PROPERTY_TRANSPARENCY = "transparency"; - public static final int PROPERTY_CODE_TRANSPARENCY = 1 << 6; - - /** - * The property name that identifies a change of this node's pickable status - * (see {@link #getPickable getPickable}). Both old value and new value will - * be null in any property change event. - */ - public static final String PROPERTY_PICKABLE = "pickable"; - public static final int PROPERTY_CODE_PICKABLE = 1 << 7; - - /** - * The property name that identifies a change of this node's children - * pickable status (see {@link #getChildrenPickable getChildrenPickable}). - * Both old value and new value will be null in any property change event. - */ - public static final String PROPERTY_CHILDREN_PICKABLE = "childrenPickable"; - public static final int PROPERTY_CODE_CHILDREN_PICKABLE = 1 << 8; - - /** - * The property name that identifies a change in the set of this node's direct children - * (see {@link #getChildrenReference getChildrenReference}, {@link #getChildrenIterator getChildrenIterator}). - * In any property change event the new value will be a reference to this node's children, - * but old value will always be null. */ - public static final String PROPERTY_CHILDREN = "children"; - public static final int PROPERTY_CODE_CHILDREN = 1 << 9; - - /** - * The property name that identifies a change of this node's parent - * (see {@link #getParent getParent}). - * Both old value and new value will be set correctly in any property change event. - */ - public static final String PROPERTY_PARENT = "parent"; - public static final int PROPERTY_CODE_PARENT = 1 << 10; - - private static final PBounds TEMP_REPAINT_BOUNDS = new PBounds(); - - /** - * The single scene graph delegate that recives low level node events. - */ - public static PSceneGraphDelegate SCENE_GRAPH_DELEGATE = null; - - /** - * PSceneGraphDelegate is an interface to recive low level node events. It together - * with PNode.SCENE_GRAPH_DELEGATE gives Piccolo users an efficient way to learn about - * low level changes in Piccolo's scene graph. Most users will not need to use this. - */ - public interface PSceneGraphDelegate { - public void nodePaintInvalidated(PNode node); - public void nodeFullBoundsInvalidated(PNode node); - } - - private transient PNode parent; - private List children; - private PBounds bounds; - private PAffineTransform transform; - private Paint paint; - private float transparency; - private MutableAttributeSet clientProperties; - private PBounds fullBoundsCache; - - private int propertyChangeParentMask = 0; - private transient SwingPropertyChangeSupport changeSupport; - private transient EventListenerList listenerList; - - private boolean pickable; - private boolean childrenPickable; - private boolean visible; - private boolean childBoundsVolatile; - private boolean paintInvalid; - private boolean childPaintInvalid; - private boolean boundsChanged; - private boolean fullBoundsInvalid; - private boolean childBoundsInvalid; - private boolean occluded; /** - * Constructs a new PNode. - *
- * By default a node's paint is null, and bounds are empty. These values - * must be set for the node to show up on the screen once it's added to - * a scene graph. - */ - public PNode() { - bounds = new PBounds(); - fullBoundsCache = new PBounds(); - transparency = 1.0f; - pickable = true; - childrenPickable = true; - visible = true; - } - - //**************************************************************** - // Animation - Methods to animate this node. - // - // Note that animation is implemented by activities (PActivity), - // so if you need more control over your animation look at the - // activities package. Each animate method creates an animation that - // will animate the node from its current state to the new state - // specified over the given duration. These methods will try to - // automatically schedule the new activity, but if the node does not - // descend from the root node when the method is called then the - // activity will not be scheduled and you must schedule it manually. - //**************************************************************** + * The property name that identifies a change in this node's client + * propertie (see {@link #getClientProperty getClientProperty}). In an + * property change event the new value will be a reference to the map of + * client properties but old value will always be null. + */ + public static final String PROPERTY_CLIENT_PROPERTIES = "clientProperties"; + public static final int PROPERTY_CODE_CLIENT_PROPERTIES = 1 << 0; - /** - * Animate this node's bounds from their current location when the activity - * starts to the specified bounds. If this node descends from the root then - * the activity will be scheduled, else the returned activity should be - * scheduled manually. If two different transform activities are scheduled - * for the same node at the same time, they will both be applied to the - * node, but the last one scheduled will be applied last on each frame, so - * it will appear to have replaced the original. Generally you will not want - * to do that. Note this method animates the node's bounds, but does not change - * the node's transform. Use animateTransformToBounds() to animate the node's - * transform instead. - * - * @param duration amount of time that the animation should take - * @return the newly scheduled activity - */ - public PInterpolatingActivity animateToBounds(double x, double y, double width, double height, long duration) { - if (duration == 0) { - setBounds(x, y, width, height); - return null; - } else { - final PBounds dst = new PBounds(x, y, width, height); - - PInterpolatingActivity ta = new PInterpolatingActivity(duration, PUtil.DEFAULT_ACTIVITY_STEP_RATE) { - private PBounds src; + /** + * The property name that identifies a change of this node's bounds (see + * {@link #getBounds getBounds}, {@link #getBoundsReference + * getBoundsReference}). In any property change event the new value will be + * a reference to this node's bounds, but old value will always be null. + */ + public static final String PROPERTY_BOUNDS = "bounds"; + public static final int PROPERTY_CODE_BOUNDS = 1 << 1; - protected void activityStarted() { - src = getBounds(); - startResizeBounds(); - super.activityStarted(); - } - - public void setRelativeTargetValue(float zeroToOne) { - PNode.this.setBounds(src.x + (zeroToOne * (dst.x - src.x)), - src.y + (zeroToOne * (dst.y - src.y)), - src.width + (zeroToOne * (dst.width - src.width)), - src.height + (zeroToOne * (dst.height - src.height))); - } - - protected void activityFinished() { - super.activityFinished(); - endResizeBounds(); - } - }; + /** + * The property name that identifies a change of this node's full bounds + * (see {@link #getFullBounds getFullBounds}, + * {@link #getFullBoundsReference getFullBoundsReference}). In any property + * change event the new value will be a reference to this node's full bounds + * cache, but old value will always be null. + */ + public static final String PROPERTY_FULL_BOUNDS = "fullBounds"; + public static final int PROPERTY_CODE_FULL_BOUNDS = 1 << 2; - addActivity(ta); - return ta; - } - } + /** + * The property name that identifies a change of this node's transform (see + * {@link #getTransform getTransform}, {@link #getTransformReference + * getTransformReference}). In any property change event the new value will + * be a reference to this node's transform, but old value will always be + * null. + */ + public static final String PROPERTY_TRANSFORM = "transform"; + public static final int PROPERTY_CODE_TRANSFORM = 1 << 3; - /** - * Animate this node from it's current transform when the activity starts - * a new transform that will fit the node into the given bounds. If this - * node descends from the root then the activity will be scheduled, else - * the returned activity should be scheduled manually. If two different - * transform activities are scheduled for the same node at the same time, - * they will both be applied to the node, but the last one scheduled will be - * applied last on each frame, so it will appear to have replaced the original. - * Generally you will not want to do that. Note this method animates the node's - * transform, but does not directly change the node's bounds rectangle. Use - * animateToBounds() to animate the node's bounds rectangle instead. - * - * @param duration amount of time that the animation should take - * @return the newly scheduled activity - */ - public PTransformActivity animateTransformToBounds(double x, double y, double width, double height, long duration) { - PAffineTransform t = new PAffineTransform(); - t.setToScale(width / getWidth(), height / getHeight()); - double scale = t.getScale(); - t.setOffset(x - (getX() * scale), y - (getY() * scale)); - return animateToTransform(t, duration); - } + /** + * The property name that identifies a change of this node's visibility (see + * {@link #getVisible getVisible}). Both old value and new value will be + * null in any property change event. + */ + public static final String PROPERTY_VISIBLE = "visible"; + public static final int PROPERTY_CODE_VISIBLE = 1 << 4; - /** - * Animate this node's transform from its current location when the - * activity starts to the specified location, scale, and rotation. If this - * node descends from the root then the activity will be scheduled, else the - * returned activity should be scheduled manually. If two different - * transform activities are scheduled for the same node at the same time, - * they will both be applied to the node, but the last one scheduled will be - * applied last on each frame, so it will appear to have replaced the - * original. Generally you will not want to do that. - * - * @param duration amount of time that the animation should take - * @param theta final theta value (in radians) for the animation - * @return the newly scheduled activity - */ - public PTransformActivity animateToPositionScaleRotation(double x, double y, double scale, double theta, long duration) { - PAffineTransform t = getTransform(); - t.setOffset(x, y); - t.setScale(scale); - t.setRotation(theta); - return animateToTransform(t, duration); - } + /** + * The property name that identifies a change of this node's paint (see + * {@link #getPaint getPaint}). Both old value and new value will be set + * correctly in any property change event. + */ + public static final String PROPERTY_PAINT = "paint"; + public static final int PROPERTY_CODE_PAINT = 1 << 5; - /** - * Animate this node's transform from its current values when the activity - * starts to the new values specified in the given transform. If this node - * descends from the root then the activity will be scheduled, else the - * returned activity should be scheduled manually. If two different - * transform activities are scheduled for the same node at the same time, - * they will both be applied to the node, but the last one scheduled will be - * applied last on each frame, so it will appear to have replaced the - * original. Generally you will not want to do that. - * - * @param destTransform the final transform value - * @param duration amount of time that the animation should take - * @return the newly scheduled activity - */ - public PTransformActivity animateToTransform(AffineTransform destTransform, long duration) { - if (duration == 0) { - setTransform(destTransform); - return null; - } else { - PTransformActivity.Target t = new PTransformActivity.Target() { - public void setTransform(AffineTransform aTransform) { - PNode.this.setTransform(aTransform); - } - public void getSourceMatrix(double[] aSource) { - PNode.this.getTransformReference(true).getMatrix(aSource); - } - }; - - PTransformActivity ta = new PTransformActivity(duration, PUtil.DEFAULT_ACTIVITY_STEP_RATE, t, destTransform); - addActivity(ta); - return ta; - } - } + /** + * The property name that identifies a change of this node's transparency + * (see {@link #getTransparency getTransparency}). Both old value and new + * value will be null in any property change event. + */ + public static final String PROPERTY_TRANSPARENCY = "transparency"; + public static final int PROPERTY_CODE_TRANSPARENCY = 1 << 6; - /** - * Animate this node's color from its current value to the new value - * specified. This meathod assumes that this nodes paint property is of - * type color. If this node descends from the root then the activity will be - * scheduled, else the returned activity should be scheduled manually. If - * two different color activities are scheduled for the same node at the - * same time, they will both be applied to the node, but the last one - * scheduled will be applied last on each frame, so it will appear to have - * replaced the original. Generally you will not want to do that. - * - * @param destColor final color value. - * @param duration amount of time that the animation should take - * @return the newly scheduled activity - */ - public PInterpolatingActivity animateToColor(Color destColor, long duration) { - if (duration == 0) { - setPaint(destColor); - return null; - } else { - PColorActivity.Target t = new PColorActivity.Target() { - public Color getColor() { - return (Color) getPaint(); - } - public void setColor(Color color) { - setPaint(color); - } - }; - - PColorActivity ca = new PColorActivity(duration, PUtil.DEFAULT_ACTIVITY_STEP_RATE, t, destColor); - addActivity(ca); - return ca; - } - } - - /** - * Animate this node's transparency from its current value to the - * new value specified. Transparency values must range from zero to one. - * If this node descends from the root then the activity will be - * scheduled, else the returned activity should be scheduled manually. - * If two different transparency activities are scheduled for the same - * node at the same time, they will both be applied to the node, but the - * last one scheduled will be applied last on each frame, so it will appear - * to have replaced the original. Generally you will not want to do that. - * - * @param zeroToOne final transparency value. - * @param duration amount of time that the animation should take - * @return the newly scheduled activity - */ - public PInterpolatingActivity animateToTransparency(float zeroToOne, long duration) { - if (duration == 0) { - setTransparency(zeroToOne); - return null; - } else { - final float dest = zeroToOne; - - PInterpolatingActivity ta = new PInterpolatingActivity(duration, PUtil.DEFAULT_ACTIVITY_STEP_RATE) { - private float source; - - protected void activityStarted() { - source = getTransparency(); - super.activityStarted(); - } - - public void setRelativeTargetValue(float zeroToOne) { - PNode.this.setTransparency(source + (zeroToOne * (dest - source))); - } - }; + /** + * The property name that identifies a change of this node's pickable status + * (see {@link #getPickable getPickable}). Both old value and new value will + * be null in any property change event. + */ + public static final String PROPERTY_PICKABLE = "pickable"; + public static final int PROPERTY_CODE_PICKABLE = 1 << 7; - addActivity(ta); - return ta; - } - } - - /** - * Schedule the given activity with the root, note that only scheduled - * activities will be stepped. If the activity is successfully added true is - * returned, else false. - * - * @param activity new activity to schedule - * @return true if the activity is successfully scheduled. - */ - public boolean addActivity(PActivity activity) { - PRoot r = getRoot(); - if (r != null) { - return r.addActivity(activity); - } - return false; - } - - // **************************************************************** - // Client Properties - Methods for managing client properties for - // this node. - // - // Client properties provide a way for programmers to attach - // extra information to a node without having to subclass it and - // add new instance variables. - //**************************************************************** + /** + * The property name that identifies a change of this node's children + * pickable status (see {@link #getChildrenPickable getChildrenPickable}). + * Both old value and new value will be null in any property change event. + */ + public static final String PROPERTY_CHILDREN_PICKABLE = "childrenPickable"; + public static final int PROPERTY_CODE_CHILDREN_PICKABLE = 1 << 8; - /** - * Return mutable attributed set of client properites associated with - * this node. - */ + /** + * The property name that identifies a change in the set of this node's + * direct children (see {@link #getChildrenReference getChildrenReference}, + * {@link #getChildrenIterator getChildrenIterator}). In any property change + * event the new value will be a reference to this node's children, but old + * value will always be null. + */ + public static final String PROPERTY_CHILDREN = "children"; + public static final int PROPERTY_CODE_CHILDREN = 1 << 9; + + /** + * The property name that identifies a change of this node's parent (see + * {@link #getParent getParent}). Both old value and new value will be set + * correctly in any property change event. + */ + public static final String PROPERTY_PARENT = "parent"; + public static final int PROPERTY_CODE_PARENT = 1 << 10; + + private static final PBounds TEMP_REPAINT_BOUNDS = new PBounds(); + + /** + * The single scene graph delegate that recives low level node events. + */ + public static PSceneGraphDelegate SCENE_GRAPH_DELEGATE = null; + + /** + * PSceneGraphDelegate is an interface to recive low level node + * events. It together with PNode.SCENE_GRAPH_DELEGATE gives Piccolo users + * an efficient way to learn about low level changes in Piccolo's scene + * graph. Most users will not need to use this. + */ + public interface PSceneGraphDelegate { + public void nodePaintInvalidated(PNode node); + + public void nodeFullBoundsInvalidated(PNode node); + } + + private transient PNode parent; + private List children; + private PBounds bounds; + private PAffineTransform transform; + private Paint paint; + private float transparency; + private MutableAttributeSet clientProperties; + private PBounds fullBoundsCache; + + private int propertyChangeParentMask = 0; + private transient SwingPropertyChangeSupport changeSupport; + private transient EventListenerList listenerList; + + private boolean pickable; + private boolean childrenPickable; + private boolean visible; + private boolean childBoundsVolatile; + private boolean paintInvalid; + private boolean childPaintInvalid; + private boolean boundsChanged; + private boolean fullBoundsInvalid; + private boolean childBoundsInvalid; + private boolean occluded; + + /** + * Constructs a new PNode. + *
+     * By default a node's paint is null, and bounds are empty. These values
+     * must be set for the node to show up on the screen once it's added to a
+     * scene graph.
+     */
+    public PNode() {
+        bounds = new PBounds();
+        fullBoundsCache = new PBounds();
+        transparency = 1.0f;
+        pickable = true;
+        childrenPickable = true;
+        visible = true;
+    }
+
+    // ****************************************************************
+    // Animation - Methods to animate this node.
+    //
+    // Note that animation is implemented by activities (PActivity),
+    // so if you need more control over your animation look at the
+    // activities package. Each animate method creates an animation that
+    // will animate the node from its current state to the new state
+    // specified over the given duration. These methods will try to
+    // automatically schedule the new activity, but if the node does not
+    // descend from the root node when the method is called then the
+    // activity will not be scheduled and you must schedule it manually.
+    // ****************************************************************
+
+    /**
+     * Animate this node's bounds from their current location when the activity
+     * starts to the specified bounds. If this node descends from the root then
+     * the activity will be scheduled, else the returned activity should be
+     * scheduled manually. If two different transform activities are scheduled
+     * for the same node at the same time, they will both be applied to the
+     * node, but the last one scheduled will be applied last on each frame, so
+     * it will appear to have replaced the original. Generally you will not want
+     * to do that. Note this method animates the node's bounds, but does not
+     * change the node's transform. Use animateTransformToBounds() to animate
+     * the node's transform instead.
+     * 
+     * @param duration amount of time that the animation should take
+     * @return the newly scheduled activity
+     */
+    public PInterpolatingActivity animateToBounds(double x, double y, double width, double height, long duration) {
+        if (duration == 0) {
+            setBounds(x, y, width, height);
+            return null;
+        }
+        else {
+            final PBounds dst = new PBounds(x, y, width, height);
+
+            PInterpolatingActivity ta = new PInterpolatingActivity(duration, PUtil.DEFAULT_ACTIVITY_STEP_RATE) {
+                private PBounds src;
+
+                protected void activityStarted() {
+                    src = getBounds();
+                    startResizeBounds();
+                    super.activityStarted();
+                }
+
+                public void setRelativeTargetValue(float zeroToOne) {
+                    PNode.this.setBounds(src.x + (zeroToOne * (dst.x - src.x)), src.y + (zeroToOne * (dst.y - src.y)),
+                            src.width + (zeroToOne * (dst.width - src.width)), src.height
+                                    + (zeroToOne * (dst.height - src.height)));
+                }
+
+                protected void activityFinished() {
+                    super.activityFinished();
+                    endResizeBounds();
+                }
+            };
+
+            addActivity(ta);
+            return ta;
+        }
+    }
+
+    /**
+     * Animate this node from it's current transform when the activity starts a
+     * new transform that will fit the node into the given bounds. If this node
+     * descends from the root then the activity will be scheduled, else the
+     * returned activity should be scheduled manually. If two different
+     * transform activities are scheduled for the same node at the same time,
+     * they will both be applied to the node, but the last one scheduled will be
+     * applied last on each frame, so it will appear to have replaced the
+     * original. Generally you will not want to do that. Note this method
+     * animates the node's transform, but does not directly change the node's
+     * bounds rectangle. Use animateToBounds() to animate the node's bounds
+     * rectangle instead.
+     * 
+     * @param duration amount of time that the animation should take
+     * @return the newly scheduled activity
+     */
+    public PTransformActivity animateTransformToBounds(double x, double y, double width, double height, long duration) {
+        PAffineTransform t = new PAffineTransform();
+        t.setToScale(width / getWidth(), height / getHeight());
+        double scale = t.getScale();
+        t.setOffset(x - (getX() * scale), y - (getY() * scale));
+        return animateToTransform(t, duration);
+    }
+
+    /**
+     * Animate this node's transform from its current location when the activity
+     * starts to the specified location, scale, and rotation. If this node
+     * descends from the root then the activity will be scheduled, else the
+     * returned activity should be scheduled manually. If two different
+     * transform activities are scheduled for the same node at the same time,
+     * they will both be applied to the node, but the last one scheduled will be
+     * applied last on each frame, so it will appear to have replaced the
+     * original. Generally you will not want to do that.
+     * 
+     * @param duration amount of time that the animation should take
+     * @param theta final theta value (in radians) for the animation
+     * @return the newly scheduled activity
+     */
+    public PTransformActivity animateToPositionScaleRotation(double x, double y, double scale, double theta,
+            long duration) {
+        PAffineTransform t = getTransform();
+        t.setOffset(x, y);
+        t.setScale(scale);
+        t.setRotation(theta);
+        return animateToTransform(t, duration);
+    }
+
+    /**
+     * Animate this node's transform from its current values when the activity
+     * starts to the new values specified in the given transform. If this node
+     * descends from the root then the activity will be scheduled, else the
+     * returned activity should be scheduled manually. If two different
+     * transform activities are scheduled for the same node at the same time,
+     * they will both be applied to the node, but the last one scheduled will be
+     * applied last on each frame, so it will appear to have replaced the
+     * original. Generally you will not want to do that.
+     * 
+     * @param destTransform the final transform value
+     * @param duration amount of time that the animation should take
+     * @return the newly scheduled activity
+     */
+    public PTransformActivity animateToTransform(AffineTransform destTransform, long duration) {
+        if (duration == 0) {
+            setTransform(destTransform);
+            return null;
+        }
+        else {
+            PTransformActivity.Target t = new PTransformActivity.Target() {
+                public void setTransform(AffineTransform aTransform) {
+                    PNode.this.setTransform(aTransform);
+                }
+
+                public void getSourceMatrix(double[] aSource) {
+                    PNode.this.getTransformReference(true).getMatrix(aSource);
+                }
+            };
+
+            PTransformActivity ta = new PTransformActivity(duration, PUtil.DEFAULT_ACTIVITY_STEP_RATE, t, destTransform);
+            addActivity(ta);
+            return ta;
+        }
+    }
+
+    /**
+     * Animate this node's color from its current value to the new value
+     * specified. This meathod assumes that this nodes paint property is of type
+     * color. If this node descends from the root then the activity will be
+     * scheduled, else the returned activity should be scheduled manually. If
+     * two different color activities are scheduled for the same node at the
+     * same time, they will both be applied to the node, but the last one
+     * scheduled will be applied last on each frame, so it will appear to have
+     * replaced the original. Generally you will not want to do that.
+     * 
+     * @param destColor final color value.
+     * @param duration amount of time that the animation should take
+     * @return the newly scheduled activity
+     */
+    public PInterpolatingActivity animateToColor(Color destColor, long duration) {
+        if (duration == 0) {
+            setPaint(destColor);
+            return null;
+        }
+        else {
+            PColorActivity.Target t = new PColorActivity.Target() {
+                public Color getColor() {
+                    return (Color) getPaint();
+                }
+
+                public void setColor(Color color) {
+                    setPaint(color);
+                }
+            };
+
+            PColorActivity ca = new PColorActivity(duration, PUtil.DEFAULT_ACTIVITY_STEP_RATE, t, destColor);
+            addActivity(ca);
+            return ca;
+        }
+    }
+
+    /**
+     * Animate this node's transparency from its current value to the new value
+     * specified. Transparency values must range from zero to one. If this node
+     * descends from the root then the activity will be scheduled, else the
+     * returned activity should be scheduled manually. If two different
+     * transparency activities are scheduled for the same node at the same time,
+     * they will both be applied to the node, but the last one scheduled will be
+     * applied last on each frame, so it will appear to have replaced the
+     * original. Generally you will not want to do that.
+     * 
+     * @param zeroToOne final transparency value.
+     * @param duration amount of time that the animation should take
+     * @return the newly scheduled activity
+     */
+    public PInterpolatingActivity animateToTransparency(float zeroToOne, long duration) {
+        if (duration == 0) {
+            setTransparency(zeroToOne);
+            return null;
+        }
+        else {
+            final float dest = zeroToOne;
+
+            PInterpolatingActivity ta = new PInterpolatingActivity(duration, PUtil.DEFAULT_ACTIVITY_STEP_RATE) {
+                private float source;
+
+                protected void activityStarted() {
+                    source = getTransparency();
+                    super.activityStarted();
+                }
+
+                public void setRelativeTargetValue(float zeroToOne) {
+                    PNode.this.setTransparency(source + (zeroToOne * (dest - source)));
+                }
+            };
+
+            addActivity(ta);
+            return ta;
+        }
+    }
+
+    /**
+     * Schedule the given activity with the root, note that only scheduled
+     * activities will be stepped. If the activity is successfully added true is
+     * returned, else false.
+     * 
+     * @param activity new activity to schedule
+     * @return true if the activity is successfully scheduled.
+     */
+    public boolean addActivity(PActivity activity) {
+        PRoot r = getRoot();
+        if (r != null) {
+            return r.addActivity(activity);
+        }
+        return false;
+    }
+
+    // ****************************************************************
+    // Client Properties - Methods for managing client properties for
+    // this node.
+    //
+    // Client properties provide a way for programmers to attach
+    // extra information to a node without having to subclass it and
+    // add new instance variables.
+    // ****************************************************************
+
+    /**
+     * Return mutable attributed set of client properites associated with this
+     * node.
+     */
     public MutableAttributeSet getClientProperties() {
         if (clientProperties == null) {
             clientProperties = new SimpleAttributeSet();
@@ -496,67 +508,71 @@
         return clientProperties;
     }
 
-	/**
-	 * Returns the value of the client attribute with the specified key. Only
-	 * attributes added with addAttribute will return
-	 * a non-null value.
-	 *
-	 * @return the value of this attribute or null
-	 */
+    /**
+     * Returns the value of the client attribute with the specified key. Only
+     * attributes added with addAttribute will return a non-null
+     * value.
+     * 
+     * @return the value of this attribute or null
+     */
     public Object getAttribute(Object key) {
         if (clientProperties == null || key == null) {
             return null;
-        } else {
+        }
+        else {
             return clientProperties.getAttribute(key);
         }
     }
 
-	/**
-	 * Add an arbitrary key/value to this node.
-	 * 
-	 * The  
-	 * If value is null this method will remove the attribute.
-	 */
-	public void addAttribute(Object key, Object value) {
-		if (value == null && clientProperties == null) return;
+    /**
+     * Add an arbitrary key/value to this node.
+     *  
+     * The  
+     * If value is null this method will remove the attribute.
+     */
+    public void addAttribute(Object key, Object value) {
+        if (value == null && clientProperties == null)
+            return;
 
-		Object oldValue = getAttribute(key);
+        Object oldValue = getAttribute(key);
 
-		if (value != oldValue) {
-			if (clientProperties == null) {
-				clientProperties = new SimpleAttributeSet();
-			}
-	
-			if (value == null) {
-				clientProperties.removeAttribute(key);
-			} else {
-				clientProperties.addAttribute(key, value);
-			}
-	
-			if (clientProperties.getAttributeCount() == 0 && clientProperties.getResolveParent() == null) {
-				clientProperties = null;
-			}
-	
-			firePropertyChange(PROPERTY_CODE_CLIENT_PROPERTIES, PROPERTY_CLIENT_PROPERTIES, null, clientProperties);
-			firePropertyChange(PROPERTY_CODE_CLIENT_PROPERTIES, key.toString(), oldValue, value);
-		}
-	}
+        if (value != oldValue) {
+            if (clientProperties == null) {
+                clientProperties = new SimpleAttributeSet();
+            }
 
-	/**
-	 * Returns an enumeration of all keys maped to attribute values values.
-	 *
-	 * @return an Enumeration over attribute keys
-	 */
-	public Enumeration getClientPropertyKeysEnumeration() {
-		if (clientProperties == null) {
-			return PUtil.NULL_ENUMERATION;
-		} else {
-			return clientProperties.getAttributeNames();
-		}
-	}
+            if (value == null) {
+                clientProperties.removeAttribute(key);
+            }
+            else {
+                clientProperties.addAttribute(key, value);
+            }
+
+            if (clientProperties.getAttributeCount() == 0 && clientProperties.getResolveParent() == null) {
+                clientProperties = null;
+            }
+
+            firePropertyChange(PROPERTY_CODE_CLIENT_PROPERTIES, PROPERTY_CLIENT_PROPERTIES, null, clientProperties);
+            firePropertyChange(PROPERTY_CODE_CLIENT_PROPERTIES, key.toString(), oldValue, value);
+        }
+    }
+
+    /**
+     * Returns an enumeration of all keys maped to attribute values values.
+     * 
+     * @return an Enumeration over attribute keys
+     */
+    public Enumeration getClientPropertyKeysEnumeration() {
+        if (clientProperties == null) {
+            return PUtil.NULL_ENUMERATION;
+        }
+        else {
+            return clientProperties.getAttributeNames();
+        }
+    }
 
     // convenience methods for attributes
 
@@ -566,2511 +582,2592 @@
     }
 
     public boolean getBooleanAttribute(Object key, boolean def) {
-        Boolean b = (Boolean)getAttribute(key);
+        Boolean b = (Boolean) getAttribute(key);
         return (b == null ? def : b.booleanValue());
     }
 
     public int getIntegerAttribute(Object key, int def) {
-        Number n = (Number)getAttribute(key);
+        Number n = (Number) getAttribute(key);
         return (n == null ? def : n.intValue());
     }
 
     public double getDoubleAttribute(Object key, double def) {
-        Number n = (Number)getAttribute(key);
+        Number n = (Number) getAttribute(key);
         return (n == null ? def : n.doubleValue());
     }
 
-	/**
-	 * @deprecated use getAttribute(Object key)instead.
-	 */
-	public Object getClientProperty(Object key) {
+    /**
+     * @deprecated use getAttribute(Object key)instead.
+     */
+    public Object getClientProperty(Object key) {
         return getAttribute(key);
-	}
+    }
 
-	/**
-	 * @deprecated use addAttribute(Object key, Object value)instead.
-	 */
-	public void addClientProperty(Object key, Object value) {
+    /**
+     * @deprecated use addAttribute(Object key, Object value)instead.
+     */
+    public void addClientProperty(Object key, Object value) {
         addAttribute(key, value);
-	}
+    }
 
-	/**
-	 * @deprecated use getClientPropertyKeysEnumerator() instead.
-	 */
-	public Iterator getClientPropertyKeysIterator() {
-		final Enumeration enumeration = getClientPropertyKeysEnumeration();
-		return new Iterator() {
-			public boolean hasNext() {
-				return enumeration.hasMoreElements();
-			}
-			public Object next() {
-				return enumeration.nextElement();
-			}
-			public void remove() {
-				throw new UnsupportedOperationException();
-			}
-		};
-	}
+    /**
+     * @deprecated use getClientPropertyKeysEnumerator() instead.
+     */
+    public Iterator getClientPropertyKeysIterator() {
+        final Enumeration enumeration = getClientPropertyKeysEnumeration();
+        return new Iterator() {
+            public boolean hasNext() {
+                return enumeration.hasMoreElements();
+            }
 
-	//****************************************************************
-	// Copying - Methods for copying this node and its descendants.
-	// Copying is implemened in terms of serialization.
-	//****************************************************************
-	
-	/**
-	 * The copy method copies this node and all of its descendents. Note
-	 * that copying is implemented in terms of java serialization. See
-	 * the serialization notes for more information.
-	 * 
-	 * @return new copy of this node or null if the node was not serializable
-	 */ 
-	public Object clone() {
-		try {
-			byte[] ser = PObjectOutputStream.toByteArray(this);
-			return (PNode) new ObjectInputStream(new ByteArrayInputStream(ser)).readObject();
-		} catch (IOException e) {
-			e.printStackTrace();
-		} catch (ClassNotFoundException e) {
-			e.printStackTrace();
-		}
-		
-		return null;
-	}
-	
-	//****************************************************************
-	// Coordinate System Conversions - Methods for converting 
-	// geometry between this nodes local coordinates and the other 
-	// major coordinate systems.
-	// 
-	// Each nodes has an affine transform that it uses to define its
-	// own coordinate system. For example if you create a new node and 
-	// add it to the canvas it will appear in the upper right corner. Its
-	// coordinate system matches the coordinate system of its parent
-	// (the root node) at this point. But if you move this node by calling
-	// node.translate() the nodes affine transform will be modified and the
-	// node will appear at a different location on the screen. The node
-	// coordinate system no longer matches the coordinate system of its
-	// parent. 
-	//
-	// This is useful because it means that the node's methods for 
-	// rendering and picking don't need to worry about the fact that 
-	// the node has been moved to another position on the screen, they
-	// keep working just like they did when it was in the upper right 
-	// hand corner of the screen.
-	// 
-	// The problem is now that each node defines its own coordinate
-	// system it is difficult to compare the positions of two node with
-	// each other. These methods are all meant to help solve that problem.
-	// 
-	// The terms used in the methods are as follows:
-	// 
-	// local - The local or base coordinate system of a node.
-	// parent - The coordinate system of a node's parent
-	// global - The topmost coordinate system, above the root node.
-	// 
-	// Normally when comparing the positions of two nodes you will 
-	// convert the local position of each node to the global coordinate
-	// system, and then compare the positions in that common coordinate
-	// system.
-	//***************************************************************
+            public Object next() {
+                return enumeration.nextElement();
+            }
 
-	/**
-	 * Transform the given point from this node's local coordinate system to
-	 * its parent's local coordinate system. Note that this will modify the point
-	 * parameter.
-	 * 
-	 * @param localPoint point in local coordinate system to be transformed.
-	 * @return point in parent's local coordinate system
-	 */ 
-	public Point2D localToParent(Point2D localPoint) {
-		if (transform == null) return localPoint;
-		return transform.transform(localPoint, localPoint);
-	}
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
 
-	/**
-	 * Transform the given dimension from this node's local coordinate system to
-	 * its parent's local coordinate system. Note that this will modify the dimension
-	 * parameter.
-	 * 
-	 * @param localDimension dimension in local coordinate system to be transformed.
-	 * @return dimension in parent's local coordinate system
-	 */ 
-	public Dimension2D localToParent(Dimension2D localDimension) {
-		if (transform == null) return localDimension;
-		return transform.transform(localDimension, localDimension);
-	}
+    // ****************************************************************
+    // Copying - Methods for copying this node and its descendants.
+    // Copying is implemened in terms of serialization.
+    // ****************************************************************
 
-	/**
-	 * Transform the given rectangle from this node's local coordinate system to
-	 * its parent's local coordinate system. Note that this will modify the rectangle
-	 * parameter.
-	 * 
-	 * @param localRectangle rectangle in local coordinate system to be transformed.
-	 * @return rectangle in parent's local coordinate system
-	 */ 
-	public Rectangle2D localToParent(Rectangle2D localRectangle) {
-		if (transform == null) return localRectangle;
-		return transform.transform(localRectangle, localRectangle);
-	}
+    /**
+     * The copy method copies this node and all of its descendents. Note that
+     * copying is implemented in terms of java serialization. See the
+     * serialization notes for more information.
+     * 
+     * @return new copy of this node or null if the node was not serializable
+     */
+    public Object clone() {
+        try {
+            byte[] ser = PObjectOutputStream.toByteArray(this);
+            return (PNode) new ObjectInputStream(new ByteArrayInputStream(ser)).readObject();
+        }
+        catch (IOException e) {
+            e.printStackTrace();
+        }
+        catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
 
-	/**
-	 * Transform the given point from this node's parent's local coordinate system to
-	 * the local coordinate system of this node. Note that this will modify the point
-	 * parameter.
-	 * 
-	 * @param parentPoint point in parent's coordinate system to be transformed.
-	 * @return point in this node's local coordinate system
-	 */ 
-	public Point2D parentToLocal(Point2D parentPoint) {
-		if (transform == null) return parentPoint;
-		try {
-			return transform.inverseTransform(parentPoint, parentPoint);
-		} catch (NoninvertibleTransformException e) {
-			e.printStackTrace();
-		}
-		return null;
-	}
+        return null;
+    }
 
-	/**
-	 * Transform the given dimension from this node's parent's local coordinate system to
-	 * the local coordinate system of this node. Note that this will modify the dimension
-	 * parameter.
-	 * 
-	 * @param parentDimension dimension in parent's coordinate system to be transformed.
-	 * @return dimension in this node's local coordinate system
-	 */ 
-	public Dimension2D parentToLocal(Dimension2D parentDimension) {
-		if (transform == null) return parentDimension;
-		return transform.inverseTransform(parentDimension, parentDimension);
-	}
+    // ****************************************************************
+    // Coordinate System Conversions - Methods for converting
+    // geometry between this nodes local coordinates and the other
+    // major coordinate systems.
+    //
+    // Each nodes has an affine transform that it uses to define its
+    // own coordinate system. For example if you create a new node and
+    // add it to the canvas it will appear in the upper right corner. Its
+    // coordinate system matches the coordinate system of its parent
+    // (the root node) at this point. But if you move this node by calling
+    // node.translate() the nodes affine transform will be modified and the
+    // node will appear at a different location on the screen. The node
+    // coordinate system no longer matches the coordinate system of its
+    // parent.
+    //
+    // This is useful because it means that the node's methods for
+    // rendering and picking don't need to worry about the fact that
+    // the node has been moved to another position on the screen, they
+    // keep working just like they did when it was in the upper right
+    // hand corner of the screen.
+    //
+    // The problem is now that each node defines its own coordinate
+    // system it is difficult to compare the positions of two node with
+    // each other. These methods are all meant to help solve that problem.
+    //
+    // The terms used in the methods are as follows:
+    //
+    // local - The local or base coordinate system of a node.
+    // parent - The coordinate system of a node's parent
+    // global - The topmost coordinate system, above the root node.
+    //
+    // Normally when comparing the positions of two nodes you will
+    // convert the local position of each node to the global coordinate
+    // system, and then compare the positions in that common coordinate
+    // system.
+    // ***************************************************************
 
-	/**
-	 * Transform the given rectangle from this node's parent's local coordinate system to
-	 * the local coordinate system of this node. Note that this will modify the rectangle
-	 * parameter.
-	 * 
-	 * @param parentRectangle rectangle in parent's coordinate system to be transformed.
-	 * @return rectangle in this node's local coordinate system
-	 */ 
-	public Rectangle2D parentToLocal(Rectangle2D parentRectangle) {
-		if (transform == null) return parentRectangle;
-		return transform.inverseTransform(parentRectangle, parentRectangle);
-	}
+    /**
+     * Transform the given point from this node's local coordinate system to its
+     * parent's local coordinate system. Note that this will modify the point
+     * parameter.
+     * 
+     * @param localPoint point in local coordinate system to be transformed.
+     * @return point in parent's local coordinate system
+     */
+    public Point2D localToParent(Point2D localPoint) {
+        if (transform == null)
+            return localPoint;
+        return transform.transform(localPoint, localPoint);
+    }
 
-	/**
-	 * Transform the given point from this node's local coordinate system to
-	 * the global coordinate system. Note that this will modify the point
-	 * parameter.
-	 * 
-	 * @param localPoint point in local coordinate system to be transformed.
-	 * @return point in global coordinates
-	 */ 
-	public Point2D localToGlobal(Point2D localPoint) {
-		PNode n = this;
-		while (n != null) {
-			localPoint = n.localToParent(localPoint);
-			n = n.parent;
-		}
-		return localPoint;
-	}
+    /**
+     * Transform the given dimension from this node's local coordinate system to
+     * its parent's local coordinate system. Note that this will modify the
+     * dimension parameter.
+     * 
+     * @param localDimension dimension in local coordinate system to be
+     *            transformed.
+     * @return dimension in parent's local coordinate system
+     */
+    public Dimension2D localToParent(Dimension2D localDimension) {
+        if (transform == null)
+            return localDimension;
+        return transform.transform(localDimension, localDimension);
+    }
 
-	/**
-	 * Transform the given dimension from this node's local coordinate system to
-	 * the global coordinate system. Note that this will modify the dimension
-	 * parameter.
-	 * 
-	 * @param localDimension dimension in local coordinate system to be transformed.
-	 * @return dimension in global coordinates
-	 */ 
-	public Dimension2D localToGlobal(Dimension2D localDimension) {
-		PNode n = this;
-		while (n != null) {
-			localDimension = n.localToParent(localDimension);
-			n = n.parent;
-		}
-		return localDimension;
-	}
+    /**
+     * Transform the given rectangle from this node's local coordinate system to
+     * its parent's local coordinate system. Note that this will modify the
+     * rectangle parameter.
+     * 
+     * @param localRectangle rectangle in local coordinate system to be
+     *            transformed.
+     * @return rectangle in parent's local coordinate system
+     */
+    public Rectangle2D localToParent(Rectangle2D localRectangle) {
+        if (transform == null)
+            return localRectangle;
+        return transform.transform(localRectangle, localRectangle);
+    }
 
-	/**
-	 * Transform the given rectangle from this node's local coordinate system to
-	 * the global coordinate system. Note that this will modify the rectangle
-	 * parameter.
-	 * 
-	 * @param localRectangle rectangle in local coordinate system to be transformed.
-	 * @return rectangle in global coordinates
-	 */ 
-	public Rectangle2D localToGlobal(Rectangle2D localRectangle) {
-		PNode n = this; 	
-		while (n != null) {
-			localRectangle = n.localToParent(localRectangle);
-			n = n.parent;
-		}
-		return localRectangle;
-	}
+    /**
+     * Transform the given point from this node's parent's local coordinate
+     * system to the local coordinate system of this node. Note that this will
+     * modify the point parameter.
+     * 
+     * @param parentPoint point in parent's coordinate system to be transformed.
+     * @return point in this node's local coordinate system
+     */
+    public Point2D parentToLocal(Point2D parentPoint) {
+        if (transform == null)
+            return parentPoint;
+        try {
+            return transform.inverseTransform(parentPoint, parentPoint);
+        }
+        catch (NoninvertibleTransformException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
 
-	/**
-	 * Transform the given point from global coordinates to this node's 
-	 * local coordinate system. Note that this will modify the point
-	 * parameter.
-	 * 
-	 * @param globalPoint point in global coordinates to be transformed.
-	 * @return point in this node's local coordinate system.
-	 */ 
-	public Point2D globalToLocal(Point2D globalPoint) {
-		if (parent != null) {
-			globalPoint = parent.globalToLocal(globalPoint);
-		}
-		return parentToLocal(globalPoint);
-	}
+    /**
+     * Transform the given dimension from this node's parent's local coordinate
+     * system to the local coordinate system of this node. Note that this will
+     * modify the dimension parameter.
+     * 
+     * @param parentDimension dimension in parent's coordinate system to be
+     *            transformed.
+     * @return dimension in this node's local coordinate system
+     */
+    public Dimension2D parentToLocal(Dimension2D parentDimension) {
+        if (transform == null)
+            return parentDimension;
+        return transform.inverseTransform(parentDimension, parentDimension);
+    }
 
-	/**
-	 * Transform the given dimension from global coordinates to this node's 
-	 * local coordinate system. Note that this will modify the dimension
-	 * parameter.
-	 * 
-	 * @param globalDimension dimension in global coordinates to be transformed.
-	 * @return dimension in this node's local coordinate system.
-	 */ 
-	public Dimension2D globalToLocal(Dimension2D globalDimension) {
-		if (parent != null) {
-			globalDimension = parent.globalToLocal(globalDimension);
-		}
-		return parentToLocal(globalDimension);
-	}
+    /**
+     * Transform the given rectangle from this node's parent's local coordinate
+     * system to the local coordinate system of this node. Note that this will
+     * modify the rectangle parameter.
+     * 
+     * @param parentRectangle rectangle in parent's coordinate system to be
+     *            transformed.
+     * @return rectangle in this node's local coordinate system
+     */
+    public Rectangle2D parentToLocal(Rectangle2D parentRectangle) {
+        if (transform == null)
+            return parentRectangle;
+        return transform.inverseTransform(parentRectangle, parentRectangle);
+    }
 
-	/**
-	 * Transform the given rectangle from global coordinates to this node's 
-	 * local coordinate system. Note that this will modify the rectangle
-	 * parameter.
-	 * 
-	 * @param globalRectangle rectangle in global coordinates to be transformed.
-	 * @return rectangle in this node's local coordinate system.
-	 */ 
-	public Rectangle2D globalToLocal(Rectangle2D globalRectangle) {
-		if (parent != null) {
-			globalRectangle = parent.globalToLocal(globalRectangle);
-		}
-		return parentToLocal(globalRectangle);
-	}
-				
-	/**
-	 * Return the transform that converts local coordinates at this node 
-	 * to the global coordinate system.
-	 * 
-	 * @return The concatenation of transforms from the top node down to this node.
-	 */
-	public PAffineTransform getLocalToGlobalTransform(PAffineTransform dest) {
-		if (parent != null) {
-			dest = parent.getLocalToGlobalTransform(dest);
-			if (transform != null) dest.concatenate(transform);
-		} else {
-			if (dest == null) {
-				dest = getTransform();
-			} else {
-				if (transform != null) {
-					dest.setTransform(transform);
-				} else {
-					dest.setToIdentity();
-				}
-			}		
-		}
-		return dest;
-	}
+    /**
+     * Transform the given point from this node's local coordinate system to the
+     * global coordinate system. Note that this will modify the point parameter.
+     * 
+     * @param localPoint point in local coordinate system to be transformed.
+     * @return point in global coordinates
+     */
+    public Point2D localToGlobal(Point2D localPoint) {
+        PNode n = this;
+        while (n != null) {
+            localPoint = n.localToParent(localPoint);
+            n = n.parent;
+        }
+        return localPoint;
+    }
 
-	/**
-	 * Return the transform that converts global coordinates 
-	 * to local coordinates of this node.
-	 * 
-	 * @return The inverse of the concatenation of transforms from the root down to this node.
-	 */
-	public PAffineTransform getGlobalToLocalTransform(PAffineTransform dest) {
-		try {
-			dest = getLocalToGlobalTransform(dest);
-			dest.setTransform(dest.createInverse());
-			return dest;
-		} catch (NoninvertibleTransformException e) {
-			e.printStackTrace();
-		}
-		return null;
-	}			
-		
-	//****************************************************************
-	// Event Listeners - Methods for adding and removing event listeners
-	// from a node.
-	// 
-	// Here methods are provided to add property change listeners and
-	// input event listeners. The property change listeners are notified 
-	// when certain properties of this node change, and the input event 
-	// listeners are notified when the nodes receives new key and mouse 
-	// events.
-	//****************************************************************
+    /**
+     * Transform the given dimension from this node's local coordinate system to
+     * the global coordinate system. Note that this will modify the dimension
+     * parameter.
+     * 
+     * @param localDimension dimension in local coordinate system to be
+     *            transformed.
+     * @return dimension in global coordinates
+     */
+    public Dimension2D localToGlobal(Dimension2D localDimension) {
+        PNode n = this;
+        while (n != null) {
+            localDimension = n.localToParent(localDimension);
+            n = n.parent;
+        }
+        return localDimension;
+    }
 
-	/**
-	 * Return the list of event listeners associated with this node.
-	 * 
-	 * @return event listener list or null
-	 */
-	public EventListenerList getListenerList() {
-		return listenerList;
-	}
-	
-	/**
-	 * Adds the specified input event listener to receive input events 
-	 * from this node.
-	 *
-	 * @param listener the new input listener
-	 */
-	public void addInputEventListener(PInputEventListener listener) {
-		if (listenerList == null) listenerList = new EventListenerList();
-		getListenerList().add(PInputEventListener.class, listener);
-	}	 
+    /**
+     * Transform the given rectangle from this node's local coordinate system to
+     * the global coordinate system. Note that this will modify the rectangle
+     * parameter.
+     * 
+     * @param localRectangle rectangle in local coordinate system to be
+     *            transformed.
+     * @return rectangle in global coordinates
+     */
+    public Rectangle2D localToGlobal(Rectangle2D localRectangle) {
+        PNode n = this;
+        while (n != null) {
+            localRectangle = n.localToParent(localRectangle);
+            n = n.parent;
+        }
+        return localRectangle;
+    }
 
-	/**
-	 * Removes the specified input event listener so that it no longer 
-	 * receives input events from this node.
-	 *
-	 * @param listener the input listener to remove
-	 */
-	public void removeInputEventListener(PInputEventListener listener) {
-		if (listenerList == null) return;
-		getListenerList().remove(PInputEventListener.class, listener);
-		if (listenerList.getListenerCount() == 0) {
-			listenerList = null;
-		}
-	}	 
-			
-	/**
-	 * Add a PropertyChangeListener to the listener list.
-	 * The listener is registered for all properties.
-	 * See the fields in PNode and subclasses that start
-	 * with PROPERTY_ to find out which properties exist.
-	 * @param listener	The PropertyChangeListener to be added
-	 */
-	public void addPropertyChangeListener(PropertyChangeListener listener) {
-		if (changeSupport == null) {
-			changeSupport = new SwingPropertyChangeSupport(this);
-		}
-		changeSupport.addPropertyChangeListener(listener);
-	}
+    /**
+     * Transform the given point from global coordinates to this node's local
+     * coordinate system. Note that this will modify the point parameter.
+     * 
+     * @param globalPoint point in global coordinates to be transformed.
+     * @return point in this node's local coordinate system.
+     */
+    public Point2D globalToLocal(Point2D globalPoint) {
+        if (parent != null) {
+            globalPoint = parent.globalToLocal(globalPoint);
+        }
+        return parentToLocal(globalPoint);
+    }
 
-	/**
-	 * Add a PropertyChangeListener for a specific property.  The listener
-	 * will be invoked only when a call on firePropertyChange names that
-	 * specific property. See the fields in PNode and subclasses that start
-	 * with PROPERTY_ to find out which properties are supported.
-	 * @param propertyName	The name of the property to listen on.
-	 * @param listener	The PropertyChangeListener to be added
-	 */
-	public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
-		if (listener == null) {
-			return;
-		}
-		if (changeSupport == null) {
-			changeSupport = new SwingPropertyChangeSupport(this);
-		}
-		changeSupport.addPropertyChangeListener(propertyName, listener);
-	}
+    /**
+     * Transform the given dimension from global coordinates to this node's
+     * local coordinate system. Note that this will modify the dimension
+     * parameter.
+     * 
+     * @param globalDimension dimension in global coordinates to be transformed.
+     * @return dimension in this node's local coordinate system.
+     */
+    public Dimension2D globalToLocal(Dimension2D globalDimension) {
+        if (parent != null) {
+            globalDimension = parent.globalToLocal(globalDimension);
+        }
+        return parentToLocal(globalDimension);
+    }
 
-	/**
-	 * Remove a PropertyChangeListener from the listener list.
-	 * This removes a PropertyChangeListener that was registered
-	 * for all properties.
-	 *
-	 * @param listener	The PropertyChangeListener to be removed
-	 */
-	public void removePropertyChangeListener(PropertyChangeListener listener) {
-		if (changeSupport != null) {
-			changeSupport.removePropertyChangeListener(listener);
-		}
-	}
+    /**
+     * Transform the given rectangle from global coordinates to this node's
+     * local coordinate system. Note that this will modify the rectangle
+     * parameter.
+     * 
+     * @param globalRectangle rectangle in global coordinates to be transformed.
+     * @return rectangle in this node's local coordinate system.
+     */
+    public Rectangle2D globalToLocal(Rectangle2D globalRectangle) {
+        if (parent != null) {
+            globalRectangle = parent.globalToLocal(globalRectangle);
+        }
+        return parentToLocal(globalRectangle);
+    }
 
-	/**
-	 * Remove a PropertyChangeListener for a specific property.
-	 *
-	 * @param propertyName	The name of the property that was listened on.
-	 * @param listener	The PropertyChangeListener to be removed
-	 */
-	public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
-		if (listener == null) {
-			return;
-		}
-		if (changeSupport == null) {
-			return;
-		}
-		changeSupport.removePropertyChangeListener(propertyName, listener);
-	}
+    /**
+     * Return the transform that converts local coordinates at this node to the
+     * global coordinate system.
+     * 
+     * @return The concatenation of transforms from the top node down to this
+     *         node.
+     */
+    public PAffineTransform getLocalToGlobalTransform(PAffineTransform dest) {
+        if (parent != null) {
+            dest = parent.getLocalToGlobalTransform(dest);
+            if (transform != null)
+                dest.concatenate(transform);
+        }
+        else {
+            if (dest == null) {
+                dest = getTransform();
+            }
+            else {
+                if (transform != null) {
+                    dest.setTransform(transform);
+                }
+                else {
+                    dest.setToIdentity();
+                }
+            }
+        }
+        return dest;
+    }
 
-	/**
-	 * Return the propertyChangeParentMask that determines which property
-	 * change events are forwared to this nodes parent so that its property
-	 * change listeners will also be notified.
-	 */
-	public int getPropertyChangeParentMask() {
-		return propertyChangeParentMask;
-	}
-	
-	/**
-	 * Set the propertyChangeParentMask that determines which property
-	 * change events are forwared to this nodes parent so that its property
-	 * change listeners will also be notified.
-	 */
-	public void setPropertyChangeParentMask(int propertyChangeParentMask) {
-		this.propertyChangeParentMask = propertyChangeParentMask;
-	}
+    /**
+     * Return the transform that converts global coordinates to local
+     * coordinates of this node.
+     * 
+     * @return The inverse of the concatenation of transforms from the root down
+     *         to this node.
+     */
+    public PAffineTransform getGlobalToLocalTransform(PAffineTransform dest) {
+        try {
+            dest = getLocalToGlobalTransform(dest);
+            dest.setTransform(dest.createInverse());
+            return dest;
+        }
+        catch (NoninvertibleTransformException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
 
-	/**
-	 * Report a bound property update to any registered listeners.
-	 * No event is fired if old and new are equal and non-null. If the propertyCode
-	 * exists in this node's propertyChangeParentMask then a property change event
-	 * will also be fired on this nodes parent.
-	 *
-	 * @param propertyCode	The code of the property changed.
-	 * @param propertyName	The programmatic name of the property that was changed.
-	 * @param oldValue	The old value of the property.
-	 * @param newValue	The new value of the property.
-	 */
+    // ****************************************************************
+    // Event Listeners - Methods for adding and removing event listeners
+    // from a node.
+    //
+    // Here methods are provided to add property change listeners and
+    // input event listeners. The property change listeners are notified
+    // when certain properties of this node change, and the input event
+    // listeners are notified when the nodes receives new key and mouse
+    // events.
+    // ****************************************************************
+
+    /**
+     * Return the list of event listeners associated with this node.
+     * 
+     * @return event listener list or null
+     */
+    public EventListenerList getListenerList() {
+        return listenerList;
+    }
+
+    /**
+     * Adds the specified input event listener to receive input events from this
+     * node.
+     * 
+     * @param listener the new input listener
+     */
+    public void addInputEventListener(PInputEventListener listener) {
+        if (listenerList == null)
+            listenerList = new EventListenerList();
+        getListenerList().add(PInputEventListener.class, listener);
+    }
+
+    /**
+     * Removes the specified input event listener so that it no longer receives
+     * input events from this node.
+     * 
+     * @param listener the input listener to remove
+     */
+    public void removeInputEventListener(PInputEventListener listener) {
+        if (listenerList == null)
+            return;
+        getListenerList().remove(PInputEventListener.class, listener);
+        if (listenerList.getListenerCount() == 0) {
+            listenerList = null;
+        }
+    }
+
+    /**
+     * Add a PropertyChangeListener to the listener list. The listener is
+     * registered for all properties. See the fields in PNode and subclasses
+     * that start with PROPERTY_ to find out which properties exist.
+     * 
+     * @param listener The PropertyChangeListener to be added
+     */
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+        if (changeSupport == null) {
+            changeSupport = new SwingPropertyChangeSupport(this);
+        }
+        changeSupport.addPropertyChangeListener(listener);
+    }
+
+    /**
+     * Add a PropertyChangeListener for a specific property. The listener will
+     * be invoked only when a call on firePropertyChange names that specific
+     * property. See the fields in PNode and subclasses that start with
+     * PROPERTY_ to find out which properties are supported.
+     * 
+     * @param propertyName The name of the property to listen on.
+     * @param listener The PropertyChangeListener to be added
+     */
+    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+        if (listener == null) {
+            return;
+        }
+        if (changeSupport == null) {
+            changeSupport = new SwingPropertyChangeSupport(this);
+        }
+        changeSupport.addPropertyChangeListener(propertyName, listener);
+    }
+
+    /**
+     * Remove a PropertyChangeListener from the listener list. This removes a
+     * PropertyChangeListener that was registered for all properties.
+     * 
+     * @param listener The PropertyChangeListener to be removed
+     */
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+        if (changeSupport != null) {
+            changeSupport.removePropertyChangeListener(listener);
+        }
+    }
+
+    /**
+     * Remove a PropertyChangeListener for a specific property.
+     * 
+     * @param propertyName The name of the property that was listened on.
+     * @param listener The PropertyChangeListener to be removed
+     */
+    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+        if (listener == null) {
+            return;
+        }
+        if (changeSupport == null) {
+            return;
+        }
+        changeSupport.removePropertyChangeListener(propertyName, listener);
+    }
+
+    /**
+     * Return the propertyChangeParentMask that determines which property change
+     * events are forwared to this nodes parent so that its property change
+     * listeners will also be notified.
+     */
+    public int getPropertyChangeParentMask() {
+        return propertyChangeParentMask;
+    }
+
+    /**
+     * Set the propertyChangeParentMask that determines which property change
+     * events are forwared to this nodes parent so that its property change
+     * listeners will also be notified.
+     */
+    public void setPropertyChangeParentMask(int propertyChangeParentMask) {
+        this.propertyChangeParentMask = propertyChangeParentMask;
+    }
+
+    /**
+     * Report a bound property update to any registered listeners. No event is
+     * fired if old and new are equal and non-null. If the propertyCode exists
+     * in this node's propertyChangeParentMask then a property change event will
+     * also be fired on this nodes parent.
+     * 
+     * @param propertyCode The code of the property changed.
+     * @param propertyName The programmatic name of the property that was
+     *            changed.
+     * @param oldValue The old value of the property.
+     * @param newValue The new value of the property.
+     */
     protected void firePropertyChange(int propertyCode, String propertyName, Object oldValue, Object newValue) {
-    	PropertyChangeEvent event = null;
-    	
-		if (changeSupport != null) {
-			event = new PropertyChangeEvent(this, propertyName, oldValue, newValue);
-			changeSupport.firePropertyChange(event);
-		}
-		if (parent != null && (propertyCode & propertyChangeParentMask) != 0) {
-			if (event == null) event = new PropertyChangeEvent(this, propertyName, oldValue, newValue);
-			parent.fireChildPropertyChange(event, propertyCode);
-		}
-	}
+        PropertyChangeEvent event = null;
 
-	/**
-	 * Called by child node to forward property change events up the node tree
-	 * so that property change listeners registered with this node will be notified
-	 * of property changes of its children nodes. For performance reason only propertyCodes
-	 * listed in the propertyChangeParentMask are forwarded.
-	 *
-	 * @param event	The property change event containing source node and changed values.
-	 * @param propertyCode	The code of the property changed.
-	 */
-	protected void fireChildPropertyChange(PropertyChangeEvent event, int propertyCode) {
-		if (changeSupport != null) {
-			changeSupport.firePropertyChange(event);
-		}
-		if (parent != null && (propertyCode & propertyChangeParentMask) != 0) {
-			parent.fireChildPropertyChange(event, propertyCode);
-		}
-	}
-	
-	//****************************************************************
-	// Bounds Geometry - Methods for setting and querying the bounds
-	// of this node.
-	// 
-	// The bounds of a node store the node's position and size in
-	// the nodes local coordinate system. Many node subclasses will need
-	// to override the setBounds method so that they can update their
-	// internal state appropriately. See PPath for an example.
-	// 
-	// Since the bounds are stored in the local coordinate system
-	// they WILL NOT change if the node is scaled, translated, or rotated.
-	// 
-	// The bounds may be accessed with either getBounds, or 
-	// getBoundsReference. The former returns a copy of the bounds
-	// the latter returns a reference to the nodes bounds that should
-	// normally not be modified. If a node is marked as volatile then
-	// it may modify its bounds before returning them from getBoundsReference,
-	// otherwise it may not.
-	//****************************************************************
+        if (changeSupport != null) {
+            event = new PropertyChangeEvent(this, propertyName, oldValue, newValue);
+            changeSupport.firePropertyChange(event);
+        }
+        if (parent != null && (propertyCode & propertyChangeParentMask) != 0) {
+            if (event == null)
+                event = new PropertyChangeEvent(this, propertyName, oldValue, newValue);
+            parent.fireChildPropertyChange(event, propertyCode);
+        }
+    }
 
-	/**
-	 * Return a copy of this node's bounds. These bounds are stored in 
-	 * the local coordinate system of this node and do not include the
-	 * bounds of any of this node's children.
-	 */
-	public PBounds getBounds() {
-		return (PBounds) getBoundsReference().clone();
-	}
+    /**
+     * Called by child node to forward property change events up the node tree
+     * so that property change listeners registered with this node will be
+     * notified of property changes of its children nodes. For performance
+     * reason only propertyCodes listed in the propertyChangeParentMask are
+     * forwarded.
+     * 
+     * @param event The property change event containing source node and changed
+     *            values.
+     * @param propertyCode The code of the property changed.
+     */
+    protected void fireChildPropertyChange(PropertyChangeEvent event, int propertyCode) {
+        if (changeSupport != null) {
+            changeSupport.firePropertyChange(event);
+        }
+        if (parent != null && (propertyCode & propertyChangeParentMask) != 0) {
+            parent.fireChildPropertyChange(event, propertyCode);
+        }
+    }
 
-	/**
-	 * Return a direct reference to this node's bounds. These bounds 
-	 * are stored in the local coordinate system of this node and do 
-	 * not include the bounds of any of this node's children. The value
-	 * returned should not be modified.
-	 */
-	public PBounds getBoundsReference() {
-		return bounds;
-	}
-	
-	/**
-	 * Notify this node that you will beging to repeadily call  
+     * For example, If you have two nodes, A and B, and you call
+     * 
+     *  
-	 * For example, If you have two nodes, A and B, and you call
-	 *  
+ * 
  * @version 1.1
  * @author Jesse Grosjean
  */
 public class PRoot extends PNode {
-	
-	/** 
-	 * The property name that identifies a change in the set of this root's
-	 * input sources (see {@link InputSource InputSource}).  In any property
-	 * change event the new value will be a reference to the list of this root's
-	 * input sources, but old value will always be null.
-	 */
-	public static final String PROPERTY_INPUT_SOURCES = "inputSources";
+
+    /**
+     * The property name that identifies a change in the set of this root's
+     * input sources (see {@link InputSource InputSource}). In any property
+     * change event the new value will be a reference to the list of this root's
+     * input sources, but old value will always be null.
+     */
+    public static final String PROPERTY_INPUT_SOURCES = "inputSources";
     public static final int PROPERTY_CODE_INPUT_SOURCES = 1 << 14;
 
-	protected transient boolean processingInputs;
-	protected transient boolean processInputsScheduled;
+    protected transient boolean processingInputs;
+    protected transient boolean processInputsScheduled;
 
-	private PInputManager defaultInputManager;
-	private transient List inputSources;
-	private transient long globalTime;
-	private PActivityScheduler activityScheduler;
+    private PInputManager defaultInputManager;
+    private transient List inputSources;
+    private transient long globalTime;
+    private PActivityScheduler activityScheduler;
 
-	/**
-	 * This interfaces is for advanced use only. If you want to implement a 
-	 * different kind of input framework then Piccolo provides you can hook
-	 * it in here.
-	 */
-	public static interface InputSource {
-		public void processInput();
-	}
-				
-	/**
-	 * Construct a new PRoot(). Note the PCanvas already creates a basic scene
-	 * graph for you so often you will not need to construct your own roots.
-	 */
-	public PRoot() {
-		super();
-		inputSources = new ArrayList();
-		globalTime = System.currentTimeMillis();
-		activityScheduler = new PActivityScheduler(this);
-	}
+    /**
+     * This interfaces is for advanced use only. If you want to implement a
+     * different kind of input framework then Piccolo provides you can hook it
+     * in here.
+     */
+    public static interface InputSource {
+        public void processInput();
+    }
 
-	//****************************************************************
-	// Activities
-	//****************************************************************
-	
-	/**
-	 * Add an activity to the activity scheduler associated with this root.
-	 * Activities are given a chance to run during each call to the roots
-	 *  
  * See the PNode.animate*() methods for an example of how to set up and run
  * different activities.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PActivity {
-	
+
     public static final int TERMINATE_WITHOUT_FINISHING = 0;
     public static final int TERMINATE_AND_FINISH = 1;
     public static final int TERMINATE_AND_FINISH_IF_STEPPING = 2;
 
-	private PActivityScheduler scheduler;
-	
-	private long startTime;
-	private long duration;
-	private long stepRate;
-	private PActivityDelegate delegate;
-	
-	private boolean stepping;
-	private long nextStepTime;
-	
-	/**
-	 * PActivityDelegate is used by classes to learn about and act on the 
-	 * different states that a PActivity goes through, such as when the activity
-	 * starts and stops stepping.
-	 */
-	public interface PActivityDelegate {
-		public void activityStarted(PActivity activity);
-		public void activityStepped(PActivity activity);
-		public void activityFinished(PActivity activity);
-	}
-	
-	/**
-	 * Constructs a new PActivity.
-	 * 
-	 * @param aDuration the amount of time that this activity should take to complete, -1 for infinite.
-	 */
-	public PActivity(long aDuration) {
-		this(aDuration, PUtil.DEFAULT_ACTIVITY_STEP_RATE);
-	}
-	
-	/**
-	 * Constructs a new PActivity.
-	 * 
-	 * @param aDuration the amount of time that this activity should take to complete, -1 for infinite.
-	 * @param aStepRate the maximum rate that this activity should receive step events.
-	 */
-	public PActivity(long aDuration, long aStepRate) {
-		this(aDuration, aStepRate, System.currentTimeMillis());
-	}
-	
-	/**
-	 * Constructs a new PActivity.
-	 * 
-	 * @param aDuration the amount of time that this activity should take to complete, -1 for infinite.
-	 * @param aStepRate the maximum rate that this activity should receive step events.
-	 * @param aStartTime the time (relative to System.currentTimeMillis()) that
-	 * this activity should start.
-	 */
-	public PActivity(long aDuration, long aStepRate, long aStartTime) {
-		duration = aDuration;
-		stepRate = aStepRate;
-		startTime = aStartTime;
-		nextStepTime = aStartTime;
-		stepping = false;
-	}
-	
-	//****************************************************************
-	// Basics
-	//****************************************************************
+    private PActivityScheduler scheduler;
 
-	/**
-	 * Return the time that this activity should start running in PRoot
-	 * global time. When this time is reached (or soon after) this activity
-	 * will have its startStepping() method called.
-	 */
-	public long getStartTime() {
-		return startTime;
-	}
-	
-	/**
-	 * Set the time that this activity should start running in PRoot
-	 * global time. When this time is reached (or soon after) this activity
-	 * will have its startStepping() method called.
-	 */
-	public void setStartTime(long aTriggerTime) {
-		startTime = aTriggerTime;
-	}
-	
-	/**
-	 * Return the amount of time that this activity should delay
-	 * between steps.
-	 */
-	public long getStepRate() {
-		return stepRate;
-	}
-	
-	/**
-	 * Set the amount of time that this activity should delay
-	 * between steps.
-	 */
-	public void setStepRate(long aStepRate) {
-		stepRate = aStepRate;
-	}
+    private long startTime;
+    private long duration;
+    private long stepRate;
+    private PActivityDelegate delegate;
 
-	public long getNextStepTime() {
-		return nextStepTime;
-	}
+    private boolean stepping;
+    private long nextStepTime;
 
-	/**
-	 * Return the amount of time that this activity should take to complete,
-	 * after the startStepping method is called.
-	 */
-	public long getDuration() {
-		return duration;
-	}
-	
-	/**
-	 * Set the amount of time that this activity should take to complete,
-	 * after the startStepping method is called.
-	 */
-	public void setDuration(long aDuration) {
-		duration = aDuration;
-	}
-	
-	public PActivityScheduler getActivityScheduler() {
-		return scheduler;
-	}
+    /**
+     * PActivityDelegate is used by classes to learn about and act on the
+     * different states that a PActivity goes through, such as when the activity
+     * starts and stops stepping.
+     */
+    public interface PActivityDelegate {
+        public void activityStarted(PActivity activity);
 
-	public void setActivityScheduler(PActivityScheduler aScheduler) {
-		scheduler = aScheduler;
-	}
-	
-	//****************************************************************
-	// Stepping
-	//****************************************************************
+        public void activityStepped(PActivity activity);
 
-	/**
-	 * Return true if this activity is stepping.
-	 */
-	public boolean isStepping() {
-		return stepping;
-	}
-	
-	/**
-	 * Return true if this activity is performing an animation. This is used
-	 * by the PCanvas to determine if it should set the render quality to
-	 * PCanvas.animatingRenderQuality or not for each frame it renders.
-	 */ 
-	protected boolean isAnimation() {
-		return false;
-	}
-	
-	/**
-	 * This method is called right before an activity is scheduled to start
-	 * running. After this method is called step() will be called until the
-	 * activity finishes.
-	 */
-	protected void activityStarted() {
-		if (delegate != null)
-			delegate.activityStarted(this);
-	}
-	
-	/**
-	 * This is the method that most activities override to perform their
-	 * behavior. It will be called repeatedly when the activity is running.
-	 * 
-	 * @param elapsedTime the amount of time that has passed relative to the activities startTime.
-	 */ 
-	protected void activityStep(long elapsedTime) {
-		if (delegate != null)
-			delegate.activityStepped(this);
-	}
-	
-	/**
-	 * This method is called after an activity is has finished running and the
-	 * activity has been removed from the PActivityScheduler queue.
-	 */
-	protected void activityFinished() {
-		if (delegate != null)
-			delegate.activityFinished(this);
-	}
-	
-	/**
-	 * Get the delegate for this activity. The delegate is notified when 
-	 * the activity starts and stops stepping.
-	 */
-	public PActivityDelegate getDelegate() {
-		return delegate;
-	}
+        public void activityFinished(PActivity activity);
+    }
 
-	/**
-	 * Set the delegate for this activity. The delegate is notified when 
-	 * the activity starts and stops stepping.
-	 */
-	public void setDelegate(PActivityDelegate delegate) {
-		this.delegate = delegate;
-	}
-		
-	//****************************************************************
-	// Controlling
-	//****************************************************************
+    /**
+     * Constructs a new PActivity.
+     * 
+     * @param aDuration the amount of time that this activity should take to
+     *            complete, -1 for infinite.
+     */
+    public PActivity(long aDuration) {
+        this(aDuration, PUtil.DEFAULT_ACTIVITY_STEP_RATE);
+    }
 
-	/**
-	 * Schedules this activity to start after the first activity has finished.
-	 * Note that no link is created between these activities, if the startTime
-	 * or duration of the first activity is later changed this activities start
-	 * time will not be updated to reflect that change.
-	 */	
-	public void startAfter(PActivity first) {
-		setStartTime(first.getStartTime() + first.getDuration());
-	}
-	
-	/**
-	 * Stop this activity immediately, and remove it from the activity
-	 * scheduler. The default termination behavior is call activityFinished
-	 * if the activity is currently stepping. Use terminate(terminationBehavior)
-	 * use a different termination behavior.
-	 */
-	public void terminate() {
-		terminate(TERMINATE_AND_FINISH_IF_STEPPING);
-	}
+    /**
+     * Constructs a new PActivity.
+     * 
+     * @param aDuration the amount of time that this activity should take to
+     *            complete, -1 for infinite.
+     * @param aStepRate the maximum rate that this activity should receive step
+     *            events.
+     */
+    public PActivity(long aDuration, long aStepRate) {
+        this(aDuration, aStepRate, System.currentTimeMillis());
+    }
 
-	/**
-	 * Stop this activity immediately, and remove it from the activity
-	 * scheduler. The termination behavior determines when and if activityStarted
-	 * and activityFinished get called. The possible termination behaviors are as
-	 * follow:
-	 * 
-	 * TERMINATE_WITHOUT_FINISHING - The method activityFinished will never get called and
-	 * so the activity will be terminated midway.
-	 * TERMINATE_AND_FINISH - The method activityFinished will always get called. And so the
-	 * activity will always end in it's completed state. If the activity has not yet started
-	 * the method activityStarted will also be called.
-	 * TERMINATE_AND_FINISH_IF_STEPPING - The method activityFinished will only be called
-	 * if the activity has previously started.
-	 */
-	public void terminate(int terminationBehavior) {
-		if (scheduler != null) {
-			scheduler.removeActivity(this);
-		}
-		
-		switch (terminationBehavior) {
-			case TERMINATE_WITHOUT_FINISHING:
-				stepping = false;
-				break;
-				
-			case TERMINATE_AND_FINISH:
-				if (stepping) {
-					stepping = false;
-					activityFinished();
-				} else {
-					activityStarted();
-					activityFinished();
-				}
+    /**
+     * Constructs a new PActivity.
+     * 
+     * @param aDuration the amount of time that this activity should take to
+     *            complete, -1 for infinite.
+     * @param aStepRate the maximum rate that this activity should receive step
+     *            events.
+     * @param aStartTime the time (relative to System.currentTimeMillis()) that
+     *            this activity should start.
+     */
+    public PActivity(long aDuration, long aStepRate, long aStartTime) {
+        duration = aDuration;
+        stepRate = aStepRate;
+        startTime = aStartTime;
+        nextStepTime = aStartTime;
+        stepping = false;
+    }
 
-				break;
-				
-			case TERMINATE_AND_FINISH_IF_STEPPING:
-				if (stepping) {
-					stepping = false;
-					activityFinished();
-				}
-				break;
-		}
-	}	
-	
-	/**
-	 * The activity scheduler calls this method and it is here
-	 * that the activity decides if it should do a step or not for the
-	 * given time.
-	 */
-	public long processStep(long currentTime) {
-		// if before start time
-		if (currentTime < startTime) {
-			return startTime - currentTime;	 
-		}
-		
-		// if past stop time
-		if (currentTime > getStopTime()) {
-			if (stepping) {
-				stepping = false;
-				scheduler.removeActivity(this);
-				activityFinished();
-			} else {
-				activityStarted();
-				scheduler.removeActivity(this);
-				activityFinished();
-			}
-			return -1;
-		}
-		
-		// else should be stepping
-		if (!stepping) {
-			activityStarted();
-			stepping = true;
-		}
+    // ****************************************************************
+    // Basics
+    // ****************************************************************
 
-		if (currentTime >= nextStepTime) {
-			activityStep(currentTime - startTime);
-			nextStepTime = currentTime + stepRate;
-		}
+    /**
+     * Return the time that this activity should start running in PRoot global
+     * time. When this time is reached (or soon after) this activity will have
+     * its startStepping() method called.
+     */
+    public long getStartTime() {
+        return startTime;
+    }
 
-		return stepRate;
-	}
-	
-	/**
-	 * Return the time when this activity should finish running. At this time
-	 * (or soon after) the stoppedStepping method will be called
-	 */
-	public long getStopTime() {
-		if (duration == -1) {
-			return Long.MAX_VALUE;
-		}
-		return startTime + duration;
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+    /**
+     * Set the time that this activity should start running in PRoot global
+     * time. When this time is reached (or soon after) this activity will have
+     * its startStepping() method called.
+     */
+    public void setStartTime(long aTriggerTime) {
+        startTime = aTriggerTime;
+    }
 
-	/**
-	 * Returns a string representation of this object for debugging purposes.
-	 */
-	public String toString() {
-		String result = super.toString().replaceAll(".*\\.", "");
-		return result + "[" + paramString() + "]";
-	}
+    /**
+     * Return the amount of time that this activity should delay between steps.
+     */
+    public long getStepRate() {
+        return stepRate;
+    }
 
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PColorActivity extends PInterpolatingActivity {
-	
-	private Color source;
-	private Color destination;
-	private Target target;
 
-	/**
-	 * Target Objects that want their color to be set by the color
-	 * activity must implement this interface.
-	 */
-	public interface Target {
+    private Color source;
+    private Color destination;
+    private Target target;
 
-		/**
-		 * This will be called by the color activity for each new
-		 * interpolated color that it computes while it is stepping.
-		 */
-		public void setColor(Color color);
+    /**
+     * Target Objects that want their color to be set by the color
+     * activity must implement this interface.
+     */
+    public interface Target {
 
-		/**
-		 * This method is called right before the color activity starts. That
-		 * way an object's color is always animated from its current color the
-		 * the destination color that is specified in the color activity.
-		 */
-		public Color getColor();
-	}
+        /**
+         * This will be called by the color activity for each new interpolated
+         * color that it computes while it is stepping.
+         */
+        public void setColor(Color color);
 
-	public PColorActivity(long duration, long stepRate, Target aTarget) {
-		this(duration, stepRate, aTarget, null);
-	}
+        /**
+         * This method is called right before the color activity starts. That
+         * way an object's color is always animated from its current color the
+         * the destination color that is specified in the color activity.
+         */
+        public Color getColor();
+    }
 
-	public PColorActivity(long duration, long stepRate, Target aTarget, Color aDestination) {
-		this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION, aTarget, aDestination);
-	}
-	
-	/**
-	 * Create a new PColorActivity.
-	 *  
-	 * @param duration the length of one loop of the activity
-	 * @param stepRate the amount of time between steps of the activity
-	 * @param loopCount number of times the activity should reschedule itself
-	 * @param mode defines how the activity interpolates between states
-	 * @param aTarget the object that the activity will be applied to and where
-	 * the source state will be taken from.
-	 * @param aDestination the destination color state
-	 */
-	public PColorActivity(long duration, long stepRate, int loopCount, int mode, Target aTarget, Color aDestination) {
-		super(duration, stepRate, loopCount, mode);
-		target = aTarget;
-		destination = aDestination;
-	}	
+    public PColorActivity(long duration, long stepRate, Target aTarget) {
+        this(duration, stepRate, aTarget, null);
+    }
 
-	protected boolean isAnimation() {
-		return true;
-	}
+    public PColorActivity(long duration, long stepRate, Target aTarget, Color aDestination) {
+        this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION, aTarget, aDestination);
+    }
 
-	/**
-	 * Return the final color that will be set on the color activities target
-	 * when the activity stops stepping.
-	 */
-	public Color getDestinationColor() {
-		return destination;
-	}
+    /**
+     * Create a new PColorActivity.
+     *  
+     * 
+     * @param duration the length of one loop of the activity
+     * @param stepRate the amount of time between steps of the activity
+     * @param loopCount number of times the activity should reschedule itself
+     * @param mode defines how the activity interpolates between states
+     * @param aTarget the object that the activity will be applied to and where
+     *            the source state will be taken from.
+     * @param aDestination the destination color state
+     */
+    public PColorActivity(long duration, long stepRate, int loopCount, int mode, Target aTarget, Color aDestination) {
+        super(duration, stepRate, loopCount, mode);
+        target = aTarget;
+        destination = aDestination;
+    }
 
-	/**
-	 * Set the final color that will be set on the color activities target when
-	 * the activity stops stepping.
-	 */
-	public void setDestinationColor(Color newDestination) {
-		destination = newDestination;
-	}
+    protected boolean isAnimation() {
+        return true;
+    }
 
-	protected void activityStarted() {
-		if (getFirstLoop()) source = target.getColor();
-		super.activityStarted();
-	}
+    /**
+     * Return the final color that will be set on the color activities target
+     * when the activity stops stepping.
+     */
+    public Color getDestinationColor() {
+        return destination;
+    }
 
-	public void setRelativeTargetValue(float zeroToOne) {
-		super.setRelativeTargetValue(zeroToOne);
-		float red = (float) (source.getRed() + (zeroToOne * (destination.getRed() - source.getRed())));
-		float green = (float) (source.getGreen() + (zeroToOne * (destination.getGreen() - source.getGreen())));
-		float blue = (float) (source.getBlue() + (zeroToOne * (destination.getBlue() - source.getBlue())));
-		float alpha = (float) (source.getAlpha() + (zeroToOne * (destination.getAlpha() - source.getAlpha())));
-		target.setColor(new Color(red/255, green/255, blue/255, alpha/255));
-	}
+    /**
+     * Set the final color that will be set on the color activities target when
+     * the activity stops stepping.
+     */
+    public void setDestinationColor(Color newDestination) {
+        destination = newDestination;
+    }
 
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+    protected void activityStarted() {
+        if (getFirstLoop())
+            source = target.getColor();
+        super.activityStarted();
+    }
 
-	/**
-	 * Returns a string representing the state of this object. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PInterpolatingActivity extends PActivity {
 
-	public static final int SOURCE_TO_DESTINATION = 1;
-	public static final int DESTINATION_TO_SOURCE = 2;
-	public static final int SOURCE_TO_DESTINATION_TO_SOURCE = 3;
+    public static final int SOURCE_TO_DESTINATION = 1;
+    public static final int DESTINATION_TO_SOURCE = 2;
+    public static final int SOURCE_TO_DESTINATION_TO_SOURCE = 3;
 
-	private int mode;
-	private boolean slowInSlowOut;
-	private int loopCount;
-	private boolean firstLoop;
-	
-	public PInterpolatingActivity(long duration, long stepRate) {
-		this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION);
-	}
+    private int mode;
+    private boolean slowInSlowOut;
+    private int loopCount;
+    private boolean firstLoop;
 
-	public PInterpolatingActivity(long duration, long stepRate, int loopCount, int mode) {
-		this(duration, stepRate, System.currentTimeMillis(), loopCount, mode);
-	}
+    public PInterpolatingActivity(long duration, long stepRate) {
+        this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION);
+    }
 
-	/**
-	 * Create a new PInterpolatingActivity.
-	 *  
-	 * @param duration the length of one loop of the activity
-	 * @param stepRate the amount of time between steps of the activity
-	 * @param startTime the time (relative to System.currentTimeMillis()) that
-	 * this activity should start.
-	 * @param loopCount number of times the activity should reschedule itself
-	 * @param mode defines how the activity interpolates between states
-	 */
-	public PInterpolatingActivity(long duration, long stepRate, long startTime, int loopCount, int mode) {
-		super(duration, stepRate, startTime);
-		this.loopCount = loopCount;
-		this.mode = mode;
-		slowInSlowOut = true;
-		firstLoop = true;
-	}
-	
-	/**
-	 * Set the amount of time that this activity should take to complete,
-	 * after the startStepping method is called. The duration must be greater
-	 * then zero so that the interpolation value can be computed.
-	 */
-	public void setDuration(long aDuration) {
-		if (aDuration <= 0) 
-			throw new IllegalArgumentException("Duration for PInterpolatingActivity must be greater then 0");
-		
-		super.setDuration(aDuration);
-	}
-	
-	//****************************************************************
-	// Basics.
-	//****************************************************************
-	
-	/**
-	 * Return the mode that defines how the activity interpolates between
-	 * states.
-	 */
-	public int getMode() {
-		return mode;
-	}
-	
-	/**
-	 * Set the mode that defines how the activity interpolates between states.
-	 */
-	public void setMode(int mode) {
-		this.mode = mode;
-	}
-	
-	/**
-	 * Return the number of times the activity should automatically reschedule
-	 * itself after it has finished.
-	 */
-	public int getLoopCount() {
-		return loopCount;
-	}
+    public PInterpolatingActivity(long duration, long stepRate, int loopCount, int mode) {
+        this(duration, stepRate, System.currentTimeMillis(), loopCount, mode);
+    }
 
-	/**
-	 * Set the number of times the activity should automatically reschedule
-	 * itself after it has finished.
-	 */
-	public void setLoopCount(int loopCount) {
-		this.loopCount = loopCount;
-	}
+    /**
+     * Create a new PInterpolatingActivity.
+     *  
+     * 
+     * @param duration the length of one loop of the activity
+     * @param stepRate the amount of time between steps of the activity
+     * @param startTime the time (relative to System.currentTimeMillis()) that
+     *            this activity should start.
+     * @param loopCount number of times the activity should reschedule itself
+     * @param mode defines how the activity interpolates between states
+     */
+    public PInterpolatingActivity(long duration, long stepRate, long startTime, int loopCount, int mode) {
+        super(duration, stepRate, startTime);
+        this.loopCount = loopCount;
+        this.mode = mode;
+        slowInSlowOut = true;
+        firstLoop = true;
+    }
 
-	/**
-	 * Return true if the activity is executing its first loop. Subclasses
-	 * normally initialize their source state on the first loop.
-	 */
-	public boolean getFirstLoop() {
-		return firstLoop;
-	}
+    /**
+     * Set the amount of time that this activity should take to complete, after
+     * the startStepping method is called. The duration must be greater then
+     * zero so that the interpolation value can be computed.
+     */
+    public void setDuration(long aDuration) {
+        if (aDuration <= 0)
+            throw new IllegalArgumentException("Duration for PInterpolatingActivity must be greater then 0");
 
-	/**
-	 * Set if the activity is executing its first loop. Subclasses normally
-	 * initialize their source state on the first loop. This method will rarely
-	 * need to be called, unless your are reusing activities.
-	 */
-	public void setFirstLoop(boolean firstLoop) {
-		this.firstLoop = firstLoop;
-	}
-		
-	public boolean getSlowInSlowOut() {
-		return slowInSlowOut;
-	}
+        super.setDuration(aDuration);
+    }
 
-	public void setSlowInSlowOut(boolean isSlowInSlowOut) {
-		slowInSlowOut = isSlowInSlowOut;
-	}
-			
-	//****************************************************************
-	// Stepping - Instead of overriding the step methods subclasses
-	// of this activity will normally override setRelativeTargetValue().
-	// This method will be called for every step of the activity with
-	// a value ranging from 0,0 (for the first step) to 1.0 (for the 
-	// final step). See PTransformActivity for an example.
-	//****************************************************************
-	
-	protected void activityStarted() {
-		super.activityStarted();
-		setRelativeTargetValueAdjustingForMode(0);
-	}
-		
-	protected void activityStep(long elapsedTime) { 
-		super.activityStep(elapsedTime);
+    // ****************************************************************
+    // Basics.
+    // ****************************************************************
 
-		float t = elapsedTime / (float) getDuration();
-				
-		t = Math.min(1, t);
-		t = Math.max(0, t);
-		
-		if (getSlowInSlowOut()) {
-			t = computeSlowInSlowOut(t);
-		}
-		
-		setRelativeTargetValueAdjustingForMode(t);		
-	}
-	
-	protected void activityFinished() {
-		setRelativeTargetValueAdjustingForMode(1);
-		super.activityFinished();
-		
-		PActivityScheduler scheduler = getActivityScheduler();
-		if (loopCount > 1) {
-			if (loopCount != Integer.MAX_VALUE) loopCount--;
-			firstLoop = false;
-			setStartTime(scheduler.getRoot().getGlobalTime());
-			scheduler.addActivity(this);
-		}
-	}
-	
-	/**
-	 * Stop this activity immediately, and remove it from the activity
-	 * scheduler. If this activity is currently running then stoppedStepping
-	 * will be called after it has been removed from the activity scheduler. 
-	 */
-	public void terminate() {
-		loopCount = 0; // set to zero so that we don't reschedule self.
-		super.terminate();
-	}
+    /**
+     * Return the mode that defines how the activity interpolates between
+     * states.
+     */
+    public int getMode() {
+        return mode;
+    }
 
-	/**
-	 * Subclasses should override this method and set the value on their
-	 * target (the object that they are modifying) accordingly.
-	 */
-	public void setRelativeTargetValue(float zeroToOne) {
-	}
-	
-	public float computeSlowInSlowOut(float zeroToOne) {
-		if (zeroToOne < 0.5) {
-			return 2.0f * zeroToOne * zeroToOne;
-		} else {
-			float complement = 1.0f - zeroToOne;
-			return 1.0f - (2.0f * complement * complement);
-		}
-	}
-	
-	protected void setRelativeTargetValueAdjustingForMode(float zeroToOne) {
-		switch (mode) {
-			case SOURCE_TO_DESTINATION:
-				break;
+    /**
+     * Set the mode that defines how the activity interpolates between states.
+     */
+    public void setMode(int mode) {
+        this.mode = mode;
+    }
 
-			case DESTINATION_TO_SOURCE:
-				zeroToOne = 1 - zeroToOne;
-				break;
+    /**
+     * Return the number of times the activity should automatically reschedule
+     * itself after it has finished.
+     */
+    public int getLoopCount() {
+        return loopCount;
+    }
 
-			case SOURCE_TO_DESTINATION_TO_SOURCE:
-				if (zeroToOne <= 0.5) {
-					zeroToOne *= 2;
-				} else {
-					zeroToOne = 1 - ((zeroToOne - 0.5f) * 2);
-				}
-				break;
-		}
+    /**
+     * Set the number of times the activity should automatically reschedule
+     * itself after it has finished.
+     */
+    public void setLoopCount(int loopCount) {
+        this.loopCount = loopCount;
+    }
 
-		setRelativeTargetValue(zeroToOne);
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
-	
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PTransformActivity extends PInterpolatingActivity {
 
-	private static PAffineTransform STATIC_TRANSFORM = new PAffineTransform();
+    private static PAffineTransform STATIC_TRANSFORM = new PAffineTransform();
 
-	private double[] source;
-	private double[] destination;
-	private Target target;
+    private double[] source;
+    private double[] destination;
+    private Target target;
 
-	/**
-	 * Target Objects that want to get transformed by the transform 
-	 * activity must implement this interface. See PNode.animateToTransform() 
-	 * for one way to do this.
-	 */
-	public interface Target {
-		
-		/**
-		 * This will be called by the transform activity for each new transform
-		 * that it computes while it is stepping.
-		 */
-		public void setTransform(AffineTransform aTransform);
-		
-		/**
-		 * This method is called right before the transform activity starts. That
-		 * way an object is always animated from its current position.
-		 */
-		public void getSourceMatrix(double[] aSource);
-	}
+    /**
+     * Target Objects that want to get transformed by the transform
+     * activity must implement this interface. See PNode.animateToTransform()
+     * for one way to do this.
+     */
+    public interface Target {
 
-	public PTransformActivity(long duration, long stepRate, Target aTarget) {
-		this(duration, stepRate, aTarget, null);
-	}
-	
-	public PTransformActivity(long duration, long stepRate, Target aTarget, AffineTransform aDestination) {
-		this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION, aTarget, aDestination);
-	}
+        /**
+         * This will be called by the transform activity for each new transform
+         * that it computes while it is stepping.
+         */
+        public void setTransform(AffineTransform aTransform);
 
-	/**
-	 * Create a new PTransformActivity.
-	 *  
-	 * @param duration the length of one loop of the activity
-	 * @param stepRate the amount of time between steps of the activity
-	 * @param loopCount number of times the activity should reschedule itself
-	 * @param mode defines how the activity interpolates between states
-	 * @param aTarget the object that the activity will be applied to and where
-	 * the source state will be taken from.
-	 * @param aDestination the destination color state
-	 */
-	public PTransformActivity(long duration, long stepRate, int loopCount, int mode, Target aTarget, AffineTransform aDestination) {
-		super(duration, stepRate, loopCount, mode);
-		source = new double[6];
-		destination = new double[6];
-		target = aTarget;
-		if (aDestination != null) aDestination.getMatrix(destination);
-	}
-	
-	protected boolean isAnimation() {
-		return true;
-	}
+        /**
+         * This method is called right before the transform activity starts.
+         * That way an object is always animated from its current position.
+         */
+        public void getSourceMatrix(double[] aSource);
+    }
 
-	/**
-	 * Return the final transform that will be set on the transform activities
-	 * target when the transform activity stops stepping.
-	 */
-	public double[] getDestinationTransform() {
-		return destination;
-	}
+    public PTransformActivity(long duration, long stepRate, Target aTarget) {
+        this(duration, stepRate, aTarget, null);
+    }
 
-	/**
-	 * Set the final transform that will be set on the transform activities
-	 * target when the transform activity stops stepping.
-	 */
-	public void setDestinationTransform(double[] newDestination) {
-		destination = newDestination;
-	}
-	
-	protected void activityStarted() { 
-		if (getFirstLoop()) target.getSourceMatrix(source);
-		super.activityStarted();
-	}
-	
-	public void setRelativeTargetValue(float zeroToOne) {
-		super.setRelativeTargetValue(zeroToOne);
+    public PTransformActivity(long duration, long stepRate, Target aTarget, AffineTransform aDestination) {
+        this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION, aTarget, aDestination);
+    }
 
-		STATIC_TRANSFORM.setTransform(source[0] + (zeroToOne * (destination[0] - source[0])),
-									  source[1] + (zeroToOne * (destination[1] - source[1])),
-									  source[2] + (zeroToOne * (destination[2] - source[2])),
-									  source[3] + (zeroToOne * (destination[3] - source[3])),
-									  source[4] + (zeroToOne * (destination[4] - source[4])),
-									  source[5] + (zeroToOne * (destination[5] - source[5])));
-									  
-		target.setTransform(STATIC_TRANSFORM);
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+    /**
+     * Create a new PTransformActivity.
+     *  
+     * 
+     * @param duration the length of one loop of the activity
+     * @param stepRate the amount of time between steps of the activity
+     * @param loopCount number of times the activity should reschedule itself
+     * @param mode defines how the activity interpolates between states
+     * @param aTarget the object that the activity will be applied to and where
+     *            the source state will be taken from.
+     * @param aDestination the destination color state
+     */
+    public PTransformActivity(long duration, long stepRate, int loopCount, int mode, Target aTarget,
+            AffineTransform aDestination) {
+        super(duration, stepRate, loopCount, mode);
+        source = new double[6];
+        destination = new double[6];
+        target = aTarget;
+        if (aDestination != null)
+            aDestination.getMatrix(destination);
+    }
 
-	/**
-	 * Returns a string representing the state of this activity. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PBasicInputEventHandler implements PInputEventListener {
 
-	private PInputEventFilter eventFilter;
+    private PInputEventFilter eventFilter;
 
-	public PBasicInputEventHandler() {
-		super();
-		eventFilter = new PInputEventFilter();		
-	}
+    public PBasicInputEventHandler() {
+        super();
+        eventFilter = new PInputEventFilter();
+    }
 
-	public void processEvent(PInputEvent event, int type) {
-		if (!acceptsEvent(event, type)) return;
+    public void processEvent(PInputEvent event, int type) {
+        if (!acceptsEvent(event, type))
+            return;
 
-		switch (type) {
-			case KeyEvent.KEY_PRESSED:
-				keyPressed(event);
-				break;
+        switch (type) {
+            case KeyEvent.KEY_PRESSED:
+                keyPressed(event);
+                break;
 
-			case KeyEvent.KEY_RELEASED:
-				keyReleased(event);
-				break;
+            case KeyEvent.KEY_RELEASED:
+                keyReleased(event);
+                break;
 
-			case KeyEvent.KEY_TYPED:
-				keyTyped(event);
-				break;
+            case KeyEvent.KEY_TYPED:
+                keyTyped(event);
+                break;
 
-			case MouseEvent.MOUSE_CLICKED:
-				mouseClicked(event);
-				break;
+            case MouseEvent.MOUSE_CLICKED:
+                mouseClicked(event);
+                break;
 
-			case MouseEvent.MOUSE_DRAGGED:
-				mouseDragged(event);
-				break;
+            case MouseEvent.MOUSE_DRAGGED:
+                mouseDragged(event);
+                break;
 
-			case MouseEvent.MOUSE_ENTERED:
-				mouseEntered(event);
-				break;
+            case MouseEvent.MOUSE_ENTERED:
+                mouseEntered(event);
+                break;
 
-			case MouseEvent.MOUSE_EXITED:
-				mouseExited(event);
-				break;
+            case MouseEvent.MOUSE_EXITED:
+                mouseExited(event);
+                break;
 
-			case MouseEvent.MOUSE_MOVED:
-				mouseMoved(event);
-				break;
+            case MouseEvent.MOUSE_MOVED:
+                mouseMoved(event);
+                break;
 
-			case MouseEvent.MOUSE_PRESSED:
-				mousePressed(event);
-				break;
+            case MouseEvent.MOUSE_PRESSED:
+                mousePressed(event);
+                break;
 
-			case MouseEvent.MOUSE_RELEASED:
-				mouseReleased(event);
-				break;
+            case MouseEvent.MOUSE_RELEASED:
+                mouseReleased(event);
+                break;
 
-			case MouseWheelEvent.WHEEL_UNIT_SCROLL:
-				mouseWheelRotated(event);
-				break;
-			
-			case MouseWheelEvent.WHEEL_BLOCK_SCROLL:
-				mouseWheelRotatedByBlock(event);
-				break;
-				
-			case FocusEvent.FOCUS_GAINED:
-				keyboardFocusGained(event);
-				break;
-				
-			case FocusEvent.FOCUS_LOST:
-				keyboardFocusLost(event);
-				break;
-							
-			default:
-				throw new RuntimeException("Bad Event Type");
-		}
-	}
-	
-	//****************************************************************
-	// Event Filter - All this event listener can be associated with a event
-	// filter. The filter accepts and rejects events based on their modifier
-	// flags and type. If the filter is null (the 
-	// default case) then it accepts all events.
-	//****************************************************************
+            case MouseWheelEvent.WHEEL_UNIT_SCROLL:
+                mouseWheelRotated(event);
+                break;
 
-	public boolean acceptsEvent(PInputEvent event, int type) {
-		return eventFilter.acceptsEvent(event, type);
-	}
+            case MouseWheelEvent.WHEEL_BLOCK_SCROLL:
+                mouseWheelRotatedByBlock(event);
+                break;
 
-	public PInputEventFilter getEventFilter() {
-		return eventFilter;
-	}
+            case FocusEvent.FOCUS_GAINED:
+                keyboardFocusGained(event);
+                break;
 
-	public void setEventFilter(PInputEventFilter newEventFilter) {
-		eventFilter = newEventFilter;
-	}
+            case FocusEvent.FOCUS_LOST:
+                keyboardFocusLost(event);
+                break;
 
-	//****************************************************************
-	// Events - Methods for handling events sent to the event listener.
-	//****************************************************************
-	 
-	public void keyPressed(PInputEvent event) {
-	}
+            default:
+                throw new RuntimeException("Bad Event Type");
+        }
+    }
 
-	public void keyReleased(PInputEvent event) {
-	}
+    // ****************************************************************
+    // Event Filter - All this event listener can be associated with a event
+    // filter. The filter accepts and rejects events based on their modifier
+    // flags and type. If the filter is null (the
+    // default case) then it accepts all events.
+    // ****************************************************************
 
-	public void keyTyped(PInputEvent event) {
-	}
+    public boolean acceptsEvent(PInputEvent event, int type) {
+        return eventFilter.acceptsEvent(event, type);
+    }
 
-	public void mouseClicked(PInputEvent event) {
-	}
+    public PInputEventFilter getEventFilter() {
+        return eventFilter;
+    }
 
-	public void mousePressed(PInputEvent event) {
-	}
+    public void setEventFilter(PInputEventFilter newEventFilter) {
+        eventFilter = newEventFilter;
+    }
 
-	public void mouseDragged(PInputEvent event) {
-	}
+    // ****************************************************************
+    // Events - Methods for handling events sent to the event listener.
+    // ****************************************************************
 
-	public void mouseEntered(PInputEvent event) {
-	}
+    public void keyPressed(PInputEvent event) {
+    }
 
-	public void mouseExited(PInputEvent event) {
-	}
+    public void keyReleased(PInputEvent event) {
+    }
 
-	public void mouseMoved(PInputEvent event) {
-	}
+    public void keyTyped(PInputEvent event) {
+    }
 
-	public void mouseReleased(PInputEvent event) {
-	}
-	
-	public void mouseWheelRotated(PInputEvent event) {
-	}
-	
-	public void mouseWheelRotatedByBlock(PInputEvent event) {
-	}
-	
-	public void keyboardFocusGained(PInputEvent event) {
-	}
-	
-	public void keyboardFocusLost(PInputEvent event) {
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+    public void mouseClicked(PInputEvent event) {
+    }
 
-	/**
-	 * Returns a string representation of this object for debugging purposes.
-	 */
-	public String toString() {
-		String result = super.toString().replaceAll(".*\\.", "");
-		return result + "[" + paramString() + "]";
-	}
+    public void mousePressed(PInputEvent event) {
+    }
 
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PDragEventHandler extends PDragSequenceEventHandler {
 
-	private PNode draggedNode;
-	private boolean moveToFrontOnPress = false;
-	
-	public PDragEventHandler() {
-		super();
-		setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK));
-	}
-	
-	protected PNode getDraggedNode() {
-		return draggedNode;
-	}
-	
-	protected void setDraggedNode(PNode draggedNode) {
-		this.draggedNode = draggedNode;
-	}
-		
-	protected boolean shouldStartDragInteraction(PInputEvent event) {
-		if (super.shouldStartDragInteraction(event)) {
-			return event.getPickedNode() != event.getTopCamera();
-		}
-		return false;
-	}
+    private PNode draggedNode;
+    private boolean moveToFrontOnPress = false;
 
-	protected void startDrag(PInputEvent event) {
-		super.startDrag(event); 	
-		draggedNode = event.getPickedNode();
-		if (moveToFrontOnPress) {
-			draggedNode.moveToFront();
-		}
-	}
+    public PDragEventHandler() {
+        super();
+        setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK));
+    }
 
-	protected void drag(PInputEvent event) {
-		super.drag(event);
-		PDimension d = event.getDeltaRelativeTo(draggedNode);		
-		draggedNode.localToParent(d);
-		draggedNode.offset(d.getWidth(), d.getHeight());
-	}
+    protected PNode getDraggedNode() {
+        return draggedNode;
+    }
 
-	protected void endDrag(PInputEvent event) {
-		super.endDrag(event);
-		draggedNode = null;
-	}	
+    protected void setDraggedNode(PNode draggedNode) {
+        this.draggedNode = draggedNode;
+    }
 
-	public boolean getMoveToFrontOnPress() {
-		return moveToFrontOnPress;
-	}
+    protected boolean shouldStartDragInteraction(PInputEvent event) {
+        if (super.shouldStartDragInteraction(event)) {
+            return event.getPickedNode() != event.getTopCamera();
+        }
+        return false;
+    }
 
-	public void setMoveToFrontOnPress(boolean moveToFrontOnPress) {
-		this.moveToFrontOnPress = moveToFrontOnPress;
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+    protected void startDrag(PInputEvent event) {
+        super.startDrag(event);
+        draggedNode = event.getPickedNode();
+        if (moveToFrontOnPress) {
+            draggedNode.moveToFront();
+        }
+    }
 
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be  
  * PDragSequenceEventHandler should be subclassed by a concrete event handler
- * that implements a particular interaction. See PPanEventHandler, PZoomEventHandler,
- * and PDragEventHandler for examples.
+ * that implements a particular interaction. See PPanEventHandler,
+ * PZoomEventHandler, and PDragEventHandler for examples.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public abstract class PDragSequenceEventHandler extends PBasicInputEventHandler {
 
-	private double minDragStartDistance = 0;
-	private transient boolean isDragging = false;
-	private transient Point2D mousePressedCanvasPoint;
-	private transient PActivity dragActivity;
-	private transient PInputEvent dragEvent;
-	private transient int sequenceInitiatedButton = MouseEvent.NOBUTTON;
+    private double minDragStartDistance = 0;
+    private transient boolean isDragging = false;
+    private transient Point2D mousePressedCanvasPoint;
+    private transient PActivity dragActivity;
+    private transient PInputEvent dragEvent;
+    private transient int sequenceInitiatedButton = MouseEvent.NOBUTTON;
 
-	public PDragSequenceEventHandler() {
-	}
+    public PDragSequenceEventHandler() {
+    }
 
-	//****************************************************************
-	// Basics
-	//****************************************************************
-	 
-	public boolean isDragging() {
-		return isDragging;
-	}
+    // ****************************************************************
+    // Basics
+    // ****************************************************************
 
-	public void setIsDragging(boolean isDragging) {
-		this.isDragging = isDragging;
-	}
-	
-	public double getMinDragStartDistance() {
-		return minDragStartDistance;
-	}
+    public boolean isDragging() {
+        return isDragging;
+    }
 
-	/**
-	 * Set the minimum distance that the mouse should be dragged (in screen coords)
-	 * before a new drag sequence is initiate.
-	 */
-	public void setMinDragStartDistance(double minDistance) {
-		minDragStartDistance = minDistance;
-	}
+    public void setIsDragging(boolean isDragging) {
+        this.isDragging = isDragging;
+    }
 
-	/**
-	 * Return the point in canvas coordinates where the mouse was last
-	 * pressed.
-	 */
-	public Point2D getMousePressedCanvasPoint() {
-		if (mousePressedCanvasPoint == null) {
-			mousePressedCanvasPoint = new Point2D.Double();
-		}
-		return mousePressedCanvasPoint;
-	}
-	
-	//****************************************************************
-	// Dragging - Methods to indicate the stages of the drag sequence.
-	//****************************************************************
-	
-	/**
-	 * Subclasses should override this method to get notified of the start of
-	 * a new drag sequence. Note that that overriding methods must still
-	 * call super.startDrag() for correct behavior.
-	 */
-	protected void startDrag(PInputEvent e) {
-		dragEvent = e;
-		startDragActivity(e);
-		setIsDragging(true);
-		e.getComponent().setInteracting(true);
-	}
+    public double getMinDragStartDistance() {
+        return minDragStartDistance;
+    }
 
-	/**
-	 * Subclasses should override this method to get notified of the drag
-	 * events in a drag sequence. Note that that overriding methods must still
-	 * call super.startDrag() for correct behavior.
-	 */
-	protected void drag(PInputEvent e) {
-		dragEvent = e;
-	}
+    /**
+     * Set the minimum distance that the mouse should be dragged (in screen
+     * coords) before a new drag sequence is initiate.
+     */
+    public void setMinDragStartDistance(double minDistance) {
+        minDragStartDistance = minDistance;
+    }
 
-	/**
-	 * Subclasses should override this method to get notified of the end event
-	 * in a drag sequence. Note that that overriding methods must still
-	 * call super.startDrag() for correct behavior.
-	 */
-	protected void endDrag(PInputEvent e) {
-		stopDragActivity(e);
-		dragEvent = null;
-		e.getComponent().setInteracting(false);
-		setIsDragging(false);
-	}
+    /**
+     * Return the point in canvas coordinates where the mouse was last pressed.
+     */
+    public Point2D getMousePressedCanvasPoint() {
+        if (mousePressedCanvasPoint == null) {
+            mousePressedCanvasPoint = new Point2D.Double();
+        }
+        return mousePressedCanvasPoint;
+    }
 
-	protected boolean shouldStartDragInteraction(PInputEvent e) {
-		return getMousePressedCanvasPoint().distance(e.getCanvasPosition()) >= getMinDragStartDistance();
-	}
-	
-	//****************************************************************
-	// Drag Activity - Used for scheduling an activity during a drag
-	// sequence. For example zooming and auto panning are implemented
-	// using this.
-	//****************************************************************
-	
-	protected PActivity getDragActivity() {
-		return dragActivity;
-	}
-	
-	protected void startDragActivity(PInputEvent aEvent) {
-		dragActivity = new PActivity(-1, PUtil.DEFAULT_ACTIVITY_STEP_RATE);
-		dragActivity.setDelegate(new PActivity.PActivityDelegate() {
-			public void activityStarted(PActivity activity) {
-				dragActivityFirstStep(dragEvent);				
-			}
-			public void activityStepped(PActivity activity) {
-				dragActivityStep(dragEvent);				
-			}
-			public void activityFinished(PActivity activity) {
-				dragActivityFinalStep(dragEvent);				
-			}
-		});
-		
-		aEvent.getCamera().getRoot().addActivity(dragActivity);
-	}
-	
-	protected void stopDragActivity(PInputEvent aEvent) {
-		dragActivity.terminate();
-		dragActivity = null;
-	}
-	
-	/**
-	 * Override this method to get notified when the drag activity
-	 * starts stepping.
-	 */
-	protected void dragActivityFirstStep(PInputEvent aEvent) {
-	}
+    // ****************************************************************
+    // Dragging - Methods to indicate the stages of the drag sequence.
+    // ****************************************************************
 
-	/**
-	 * During a drag sequence an activity is scheduled that runs continuously
-	 * while the drag sequence is active. This can be used to support some 
-	 * additional behavior that is not driven directly by mouse events. For
-	 * example PZoomEventHandler uses it for zooming and PPanEventHandler uses
-	 * it for auto panning.
-	 */
-	protected void dragActivityStep(PInputEvent aEvent) {
-	}
+    /**
+     * Subclasses should override this method to get notified of the start of a
+     * new drag sequence. Note that that overriding methods must still call
+     * super.startDrag() for correct behavior.
+     */
+    protected void startDrag(PInputEvent e) {
+        dragEvent = e;
+        startDragActivity(e);
+        setIsDragging(true);
+        e.getComponent().setInteracting(true);
+    }
 
-	/**
-	 * Override this method to get notified when the drag activity
-	 * stops stepping.
-	 */
-	protected void dragActivityFinalStep(PInputEvent aEvent) {
-	}
+    /**
+     * Subclasses should override this method to get notified of the drag events
+     * in a drag sequence. Note that that overriding methods must still call
+     * super.startDrag() for correct behavior.
+     */
+    protected void drag(PInputEvent e) {
+        dragEvent = e;
+    }
 
-	//****************************************************************
-	// Events - subclasses should not override these methods, instead
-	// override the appropriate drag method.
-	//****************************************************************
-	 
-	public void mousePressed(PInputEvent e) {
-		super.mousePressed(e);
-		
-		if (sequenceInitiatedButton == MouseEvent.NOBUTTON) {
-			sequenceInitiatedButton = e.getButton();
-		} else {
-			return;
-		}
-		
-		getMousePressedCanvasPoint().setLocation(e.getCanvasPosition());
-		if (!isDragging()) {
-			if (shouldStartDragInteraction(e)) {
-				startDrag(e);
-			}
-		}
-	}
-	
-	public void mouseDragged(PInputEvent e) {
-		super.mouseDragged(e);
+    /**
+     * Subclasses should override this method to get notified of the end event
+     * in a drag sequence. Note that that overriding methods must still call
+     * super.startDrag() for correct behavior.
+     */
+    protected void endDrag(PInputEvent e) {
+        stopDragActivity(e);
+        dragEvent = null;
+        e.getComponent().setInteracting(false);
+        setIsDragging(false);
+    }
 
-		if (sequenceInitiatedButton != MouseEvent.NOBUTTON) {
-			if (!isDragging()) {
-				if (shouldStartDragInteraction(e)) {
-					startDrag(e);
-				}
-				return;
-			}
-			drag(e);
-		}
-	}
-	
-	public void mouseReleased(PInputEvent e) {
-		super.mouseReleased(e);
-		if (sequenceInitiatedButton == e.getButton()) {
-			if (isDragging()) endDrag(e);
-			sequenceInitiatedButton = MouseEvent.NOBUTTON;
-		}
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+    protected boolean shouldStartDragInteraction(PInputEvent e) {
+        return getMousePressedCanvasPoint().distance(e.getCanvasPosition()) >= getMinDragStartDistance();
+    }
 
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be  
- * In addition is has methods to get the mouse position and delta in a variety 
+ * In addition is has methods to get the mouse position and delta in a variety
  * of coordinate systems.
  *  
- * Last of all it provides access to the dispatch manager that can be queried
- * to find the current mouse over, mouse focus, and keyboard focus.
+ * Last of all it provides access to the dispatch manager that can be queried to
+ * find the current mouse over, mouse focus, and keyboard focus.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PInputEvent {
 
-	private InputEvent inputEvent;
-	private PPickPath pickPath;
-	private PInputManager inputManager;
-	private boolean handled;
-		
-	public PInputEvent(PInputManager inputManager, InputEvent event) {
-		super();
-		inputEvent = event;
-		this.inputManager = inputManager;
-	}
+    private InputEvent inputEvent;
+    private PPickPath pickPath;
+    private PInputManager inputManager;
+    private boolean handled;
 
-	public void pushCursor(Cursor cursor) {
-		PComponent component = getTopCamera().getComponent();
-		component.pushCursor(cursor);
-	}
-	
-	public void popCursor() {
-		PComponent component = getTopCamera().getComponent();
-		component.popCursor();
-	}
-	
-	//****************************************************************
-	// Accessing Picked Objects - Methods to access the objects associated
-	// with this event.
-	//   
-	// Cameras can view layers that have 
-	// other cameras on them, so events may be arriving through a stack
-	// of many cameras. The getCamera() method returns the bottommost
-	// camera on that stack. The getTopCamera method returns the topmost
-	// camera on that stack, this is also the camera through which the
-	// event originated.
-	//****************************************************************
-	
-	/**
-	 * Return the bottom most camera that is currently painting. If you are
-	 * using internal cameras this may be different then what is returned by
-	 * getTopCamera.
-	 */
-	public PCamera getCamera() {
-		return getPath().getBottomCamera();
-	}
-	
-	/**
-	 * Return the topmost camera this is painting. This is the camera assocaited
-	 * with the PCanvas that requested the current repaint.
-	 */
-	public PCamera getTopCamera() {
-		return getPath().getTopCamera();
-	}
-	
-	/**
-	 * Get the canvas associated with the top camera. This is the canvas where the
-	 * originating swing event came from.
-	 */
-	public PComponent getComponent() {
-		return getTopCamera().getComponent();
-	}
-	
-	/**
-	 * Return the input manager that dispatched this event. You can use this input
-	 * manager to find the current mouse focus, mouse over, and key focus nodes.
-	 * You can also set a new key focus node.
-	 */
-	public PInputManager getInputManager() {
-		return inputManager;
-	}
-	
-	/**
-	 * Return the PPickPath associated with this input event.
-	 */
-	public PPickPath getPath() {
-		return pickPath;
-	}
-	
-	public void setPath(PPickPath path) {
-		pickPath = path;
-	}
-	
-	/**
-	 * Return the bottom node on the current pickpath, that is the picked node
-	 * furthest from the root node.
-	 */
-	public PNode getPickedNode() {
-		return pickPath.getPickedNode();
-	}
-	
-	//****************************************************************
-	// Basics
-	//****************************************************************
-	 
-	public int getKeyCode() {
-		if (isKeyEvent()) {
-			KeyEvent e = (KeyEvent) inputEvent;
-			return e.getKeyCode();
-		}
-		throw new IllegalStateException("Can't get keycode from mouse event");
-	}
-
-	public char getKeyChar() {
-		if (isKeyEvent()) {
-			KeyEvent e = (KeyEvent) inputEvent;
-			return e.getKeyChar();
-		}
-		throw new IllegalStateException("Can't get keychar from mouse event");
-	}
-	
-	public int getKeyLocation() {
-		if (isKeyEvent()) {
-			KeyEvent e = (KeyEvent) inputEvent;
-			return e.getKeyLocation();
-		}
-		throw new IllegalStateException("Can't get keylocation from mouse event");
-	}
-	
-	public boolean isActionKey() {
-		if (isKeyEvent()) {
-			KeyEvent e = (KeyEvent) inputEvent;
-			return e.isActionKey();
-		}
-		throw new IllegalStateException("Can't get isActionKey from mouse event");
-	}
-		
-	public int getModifiers() {
-		if (!isFocusEvent()) {
-			return inputEvent.getModifiers();
-		}
-		throw new IllegalStateException("Can't get modifiers from focus event");
-	}
-
-	public int getModifiersEx() {
-		if (!isFocusEvent()) {
-			return inputEvent.getModifiersEx();
-		}
-		throw new IllegalStateException("Can't get modifiers ex from focus event");
-	}
-
-	public int getClickCount() {
-		if (isMouseEvent()) {
-			return ((MouseEvent)inputEvent).getClickCount();
-		}
-		throw new IllegalStateException("Can't get clickcount from key event");
-	}
-	
-	public long getWhen() {
-		if (!isFocusEvent()) {
-			return inputEvent.getWhen();
-		}
-		throw new IllegalStateException("Can't get when from focus event");
-	}
-	
-	public boolean isAltDown() {
-		if (!isFocusEvent()) {
-			return inputEvent.isAltDown();
-		}
-		throw new IllegalStateException("Can't get altdown from focus event");
-	}
-
-	public boolean isControlDown() {
-		if (!isFocusEvent()) {
-			return inputEvent.isControlDown();
-		}
-		throw new IllegalStateException("Can't get controldown from focus event");
-	}
-
-	public boolean isMetaDown() {
-		if (!isFocusEvent()) {
-			return inputEvent.isMetaDown();
-		}
-		throw new IllegalStateException("Can't get modifiers from focus event");
-	}
-
-	public boolean isShiftDown() {
-		if (!isFocusEvent()) {
-			return inputEvent.isShiftDown();
-		}
-		throw new IllegalStateException("Can't get shiftdown from focus event");
-	}
-
-	public boolean isLeftMouseButton() {
-		if (isMouseEvent()) {
-			return SwingUtilities.isLeftMouseButton((MouseEvent)getSourceSwingEvent());
-		}
-		throw new IllegalStateException("Can't get isLeftMouseButton from focus event");
-	}
-
-	public boolean isMiddleMouseButton() {
-		if (isMouseEvent()) {
-			return SwingUtilities.isMiddleMouseButton((MouseEvent)getSourceSwingEvent());
-		}
-		throw new IllegalStateException("Can't get isMiddleMouseButton from focus event");
-	}
-
-	public boolean isRightMouseButton() {
-		if (isMouseEvent()) {
-			return SwingUtilities.isRightMouseButton((MouseEvent)getSourceSwingEvent());
-		}
-		throw new IllegalStateException("Can't get isRightMouseButton from focus event");
-	}
-
-	/**
-	 * Return true if another event handler has already handled this event. Event handlers should use
-	 * this as a hint before handling the event themselves and possibly reject events that have 
-	 * already been handled.
-	 */
-	public boolean isHandled() {
-		return handled;
-	}
-
-	/**
-	 * Set that this event has been handled by an event handler. This is a relaxed for of consuming events.
-	 * The event will continue to get dispatched to event handlers even after it is marked as handled, but
-	 * other event handlers that might conflict are expected to ignore events that have already been handled.
-	 */
-	public void setHandled(boolean handled) {
-		this.handled = handled;
-	}
-
-	public int getButton() {
-		if (isMouseEvent()) {
-			return ((MouseEvent)inputEvent).getButton();			
-		}
-		throw new IllegalStateException("Can't get button from key event");
-	}
-
-    public int getWheelRotation() {
-		if (isMouseWheelEvent()) {
-			return ((MouseWheelEvent) inputEvent).getWheelRotation();
-		}
-		throw new IllegalStateException("Can't get wheel rotation from non-wheel event");
+    public PInputEvent(PInputManager inputManager, InputEvent event) {
+        super();
+        inputEvent = event;
+        this.inputManager = inputManager;
     }
 
-	public InputEvent getSourceSwingEvent() {
-		return inputEvent;
-	}
+    public void pushCursor(Cursor cursor) {
+        PComponent component = getTopCamera().getComponent();
+        component.pushCursor(cursor);
+    }
 
-	//****************************************************************
-	// Classification - Methods to distinguish between mouse and key
-	// events.
-	//****************************************************************
-	 
-	public boolean isKeyEvent() {
-		return inputEvent instanceof KeyEvent;
-	}
+    public void popCursor() {
+        PComponent component = getTopCamera().getComponent();
+        component.popCursor();
+    }
 
-	public boolean isMouseEvent() {
-		return inputEvent instanceof MouseEvent;
-	}
-	
-	public boolean isMouseWheelEvent() {
-		return inputEvent instanceof MouseWheelEvent;
-	}
-	
-	public boolean isFocusEvent() {
-		return inputEvent == null;
-	}
-	
-	public boolean isMouseEnteredOrMouseExited() {
-		if (isMouseEvent()) {
-			return inputEvent.getID() == MouseEvent.MOUSE_ENTERED || 
-				   inputEvent.getID() == MouseEvent.MOUSE_EXITED;
-		}
-		return false;
-	}
-	
-	/**
-	 * Returns whether or not this event is a popup menu trigger event for the
-	 * platform. Must not be called if this event isn't a mouse event.
-	 *  Note: Popup menus are triggered differently on different
-	 * systems. Therefore,  
+    // Cameras can view layers that have
+    // other cameras on them, so events may be arriving through a stack
+    // of many cameras. The getCamera() method returns the bottommost
+    // camera on that stack. The getTopCamera method returns the topmost
+    // camera on that stack, this is also the camera through which the
+    // event originated.
+    // ****************************************************************
 
-	/**
-	 * Return the delta between the last and current mouse 
-	 * position in PCanvas coordinates.
-	 */
-	public PDimension getCanvasDelta() {
-		Point2D last = inputManager.getLastCanvasPosition();	
-		Point2D current = inputManager.getCurrentCanvasPosition();
-		return new PDimension(current.getX() - last.getX(), current.getY() - last.getY());
-	}
+    /**
+     * Return the bottom most camera that is currently painting. If you are
+     * using internal cameras this may be different then what is returned by
+     * getTopCamera.
+     */
+    public PCamera getCamera() {
+        return getPath().getBottomCamera();
+    }
 
-	/**
-	 * Return the mouse position relative to a given node on the pick path.
-	 */
-	public Point2D getPositionRelativeTo(PNode nodeOnPath) {
-		Point2D r = getCanvasPosition();
-		return pickPath.canvasToLocal(r, nodeOnPath);
-	}
-	
-	/**
-	 * Return the delta between the last and current mouse positions
-	 * relative to a given node on the pick path.
-	 */
-	public PDimension getDeltaRelativeTo(PNode nodeOnPath) {
-		PDimension r = getCanvasDelta();
-		return (PDimension) pickPath.canvasToLocal(r, nodeOnPath);
-	}
-	
-	/**
-	 * Return the mouse position transformed through the view transform of
-	 * the bottom camera.
-	 */
-	public Point2D getPosition() {
-		Point2D r = getCanvasPosition();
-		pickPath.canvasToLocal(r, getCamera());
-		return getCamera().localToView(r);
-	}
+    /**
+     * Return the topmost camera this is painting. This is the camera assocaited
+     * with the PCanvas that requested the current repaint.
+     */
+    public PCamera getTopCamera() {
+        return getPath().getTopCamera();
+    }
 
-	/**
-	 * Return the delta between the last and current mouse positions 
-	 * transformed through the view transform of the bottom camera.
-	 */
-	public PDimension getDelta() {
-		PDimension r = getCanvasDelta();
-		pickPath.canvasToLocal(r, getCamera());
-		return (PDimension) getCamera().localToView(r);
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+    /**
+     * Get the canvas associated with the top camera. This is the canvas where
+     * the originating swing event came from.
+     */
+    public PComponent getComponent() {
+        return getTopCamera().getComponent();
+    }
 
-	/**
-	 * Returns a string representation of this object for debugging purposes.
-	 */
-	public String toString() {
-		StringBuffer result = new StringBuffer();
+    /**
+     * Return the input manager that dispatched this event. You can use this
+     * input manager to find the current mouse focus, mouse over, and key focus
+     * nodes. You can also set a new key focus node.
+     */
+    public PInputManager getInputManager() {
+        return inputManager;
+    }
 
-		result.append(super.toString().replaceAll(".*\\.", ""));
-		result.append('[');
-		if (handled) {
-			result.append("handled");
-		}
-		result.append(']');
+    /**
+     * Return the PPickPath associated with this input event.
+     */
+    public PPickPath getPath() {
+        return pickPath;
+    }
 
-		return result.toString();
-	}	
+    public void setPath(PPickPath path) {
+        pickPath = path;
+    }
+
+    /**
+     * Return the bottom node on the current pickpath, that is the picked node
+     * furthest from the root node.
+     */
+    public PNode getPickedNode() {
+        return pickPath.getPickedNode();
+    }
+
+    // ****************************************************************
+    // Basics
+    // ****************************************************************
+
+    public int getKeyCode() {
+        if (isKeyEvent()) {
+            KeyEvent e = (KeyEvent) inputEvent;
+            return e.getKeyCode();
+        }
+        throw new IllegalStateException("Can't get keycode from mouse event");
+    }
+
+    public char getKeyChar() {
+        if (isKeyEvent()) {
+            KeyEvent e = (KeyEvent) inputEvent;
+            return e.getKeyChar();
+        }
+        throw new IllegalStateException("Can't get keychar from mouse event");
+    }
+
+    public int getKeyLocation() {
+        if (isKeyEvent()) {
+            KeyEvent e = (KeyEvent) inputEvent;
+            return e.getKeyLocation();
+        }
+        throw new IllegalStateException("Can't get keylocation from mouse event");
+    }
+
+    public boolean isActionKey() {
+        if (isKeyEvent()) {
+            KeyEvent e = (KeyEvent) inputEvent;
+            return e.isActionKey();
+        }
+        throw new IllegalStateException("Can't get isActionKey from mouse event");
+    }
+
+    public int getModifiers() {
+        if (!isFocusEvent()) {
+            return inputEvent.getModifiers();
+        }
+        throw new IllegalStateException("Can't get modifiers from focus event");
+    }
+
+    public int getModifiersEx() {
+        if (!isFocusEvent()) {
+            return inputEvent.getModifiersEx();
+        }
+        throw new IllegalStateException("Can't get modifiers ex from focus event");
+    }
+
+    public int getClickCount() {
+        if (isMouseEvent()) {
+            return ((MouseEvent) inputEvent).getClickCount();
+        }
+        throw new IllegalStateException("Can't get clickcount from key event");
+    }
+
+    public long getWhen() {
+        if (!isFocusEvent()) {
+            return inputEvent.getWhen();
+        }
+        throw new IllegalStateException("Can't get when from focus event");
+    }
+
+    public boolean isAltDown() {
+        if (!isFocusEvent()) {
+            return inputEvent.isAltDown();
+        }
+        throw new IllegalStateException("Can't get altdown from focus event");
+    }
+
+    public boolean isControlDown() {
+        if (!isFocusEvent()) {
+            return inputEvent.isControlDown();
+        }
+        throw new IllegalStateException("Can't get controldown from focus event");
+    }
+
+    public boolean isMetaDown() {
+        if (!isFocusEvent()) {
+            return inputEvent.isMetaDown();
+        }
+        throw new IllegalStateException("Can't get modifiers from focus event");
+    }
+
+    public boolean isShiftDown() {
+        if (!isFocusEvent()) {
+            return inputEvent.isShiftDown();
+        }
+        throw new IllegalStateException("Can't get shiftdown from focus event");
+    }
+
+    public boolean isLeftMouseButton() {
+        if (isMouseEvent()) {
+            return SwingUtilities.isLeftMouseButton((MouseEvent) getSourceSwingEvent());
+        }
+        throw new IllegalStateException("Can't get isLeftMouseButton from focus event");
+    }
+
+    public boolean isMiddleMouseButton() {
+        if (isMouseEvent()) {
+            return SwingUtilities.isMiddleMouseButton((MouseEvent) getSourceSwingEvent());
+        }
+        throw new IllegalStateException("Can't get isMiddleMouseButton from focus event");
+    }
+
+    public boolean isRightMouseButton() {
+        if (isMouseEvent()) {
+            return SwingUtilities.isRightMouseButton((MouseEvent) getSourceSwingEvent());
+        }
+        throw new IllegalStateException("Can't get isRightMouseButton from focus event");
+    }
+
+    /**
+     * Return true if another event handler has already handled this event.
+     * Event handlers should use this as a hint before handling the event
+     * themselves and possibly reject events that have already been handled.
+     */
+    public boolean isHandled() {
+        return handled;
+    }
+
+    /**
+     * Set that this event has been handled by an event handler. This is a
+     * relaxed for of consuming events. The event will continue to get
+     * dispatched to event handlers even after it is marked as handled, but
+     * other event handlers that might conflict are expected to ignore events
+     * that have already been handled.
+     */
+    public void setHandled(boolean handled) {
+        this.handled = handled;
+    }
+
+    public int getButton() {
+        if (isMouseEvent()) {
+            return ((MouseEvent) inputEvent).getButton();
+        }
+        throw new IllegalStateException("Can't get button from key event");
+    }
+
+    public int getWheelRotation() {
+        if (isMouseWheelEvent()) {
+            return ((MouseWheelEvent) inputEvent).getWheelRotation();
+        }
+        throw new IllegalStateException("Can't get wheel rotation from non-wheel event");
+    }
+
+    public InputEvent getSourceSwingEvent() {
+        return inputEvent;
+    }
+
+    // ****************************************************************
+    // Classification - Methods to distinguish between mouse and key
+    // events.
+    // ****************************************************************
+
+    public boolean isKeyEvent() {
+        return inputEvent instanceof KeyEvent;
+    }
+
+    public boolean isMouseEvent() {
+        return inputEvent instanceof MouseEvent;
+    }
+
+    public boolean isMouseWheelEvent() {
+        return inputEvent instanceof MouseWheelEvent;
+    }
+
+    public boolean isFocusEvent() {
+        return inputEvent == null;
+    }
+
+    public boolean isMouseEnteredOrMouseExited() {
+        if (isMouseEvent()) {
+            return inputEvent.getID() == MouseEvent.MOUSE_ENTERED || inputEvent.getID() == MouseEvent.MOUSE_EXITED;
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether or not this event is a popup menu trigger event for the
+     * platform. Must not be called if this event isn't a mouse event.
+     *  
+     * Note: Popup menus are triggered differently on different systems.
+     * Therefore,  
  * To be accepted events must contain all the modifiers listed in the andMask,
- * at least one of the modifiers listed in the orMask, and none of the 
- * modifiers listed in the notMask. The event filter also lets you specify specific
- * event types (mousePressed, released, ...) to accept or reject.
+ * at least one of the modifiers listed in the orMask, and none of the modifiers
+ * listed in the notMask. The event filter also lets you specify specific event
+ * types (mousePressed, released, ...) to accept or reject.
  *  
  * If the event filter is set to consume, then it will call consume on any event
  * that it successfully accepts.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PInputEventFilter {
 
-	public static int ALL_MODIFIERS_MASK = InputEvent.BUTTON1_MASK |
-										   InputEvent.BUTTON2_MASK |
-										   InputEvent.BUTTON3_MASK |
-										   InputEvent.SHIFT_MASK |
-										   InputEvent.CTRL_MASK |
-										   InputEvent.ALT_MASK |
-										   InputEvent.ALT_GRAPH_MASK |
-										   InputEvent.META_MASK;
+    public static int ALL_MODIFIERS_MASK = InputEvent.BUTTON1_MASK | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK
+            | InputEvent.SHIFT_MASK | InputEvent.CTRL_MASK | InputEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK
+            | InputEvent.META_MASK;
 
-	private int andMask;
-	private int orMask;
-	private int notMask;
-	private short clickCount = -1;
+    private int andMask;
+    private int orMask;
+    private int notMask;
+    private short clickCount = -1;
 
-	private boolean marksAcceptedEventsAsHandled = false;
-	
-	private boolean acceptsAlreadyHandledEvents = false;
-	private boolean acceptsKeyPressed = true;
-	private boolean acceptsKeyReleased = true;
-	private boolean acceptsKeyTyped = true;
+    private boolean marksAcceptedEventsAsHandled = false;
 
-	private boolean acceptsMouseClicked = true;
-	private boolean acceptsMouseDragged = true;
-	private boolean acceptsMouseEntered = true;
-	private boolean acceptsMouseExited = true;
-	private boolean acceptsMouseMoved = true;
-	private boolean acceptsMousePressed = true;
-	private boolean acceptsMouseReleased = true;
-	private boolean acceptsMouseWheelRotated = true;
-	private boolean acceptsFocusEvents = true;
+    private boolean acceptsAlreadyHandledEvents = false;
+    private boolean acceptsKeyPressed = true;
+    private boolean acceptsKeyReleased = true;
+    private boolean acceptsKeyTyped = true;
 
-	public PInputEventFilter() {
-		acceptEverything();
-	}
+    private boolean acceptsMouseClicked = true;
+    private boolean acceptsMouseDragged = true;
+    private boolean acceptsMouseEntered = true;
+    private boolean acceptsMouseExited = true;
+    private boolean acceptsMouseMoved = true;
+    private boolean acceptsMousePressed = true;
+    private boolean acceptsMouseReleased = true;
+    private boolean acceptsMouseWheelRotated = true;
+    private boolean acceptsFocusEvents = true;
 
-	public PInputEventFilter(int aAndMask) {
-		this();
-		andMask = aAndMask;
-	}
+    public PInputEventFilter() {
+        acceptEverything();
+    }
 
-	public PInputEventFilter(int aAndMask, int aNotMask) {
-		this(aAndMask);
-		notMask = aNotMask;
-	}
+    public PInputEventFilter(int aAndMask) {
+        this();
+        andMask = aAndMask;
+    }
 
-	public boolean acceptsEvent(PInputEvent aEvent, int type) {
-		boolean aResult = false;
-		int modifiers = 0;
-		
-		if (!aEvent.isFocusEvent()) {
-			modifiers = aEvent.getModifiers();
-		}
-				
-		if ((!aEvent.isHandled() || acceptsAlreadyHandledEvents) &&
-			(modifiers == 0 ||					   	// if no modifiers then ignore modifier constraints, ELSE
-			(modifiers & andMask) == andMask &&  	// must have all modifiers from the AND mask and
-			(modifiers & orMask) != 0 &&		   	// must have at least one modifier from the OR mask and
-			(modifiers & notMask) == 0)) {		   	// can't have any modifiers from the NOT mask
+    public PInputEventFilter(int aAndMask, int aNotMask) {
+        this(aAndMask);
+        notMask = aNotMask;
+    }
 
-			if (aEvent.isMouseEvent() && clickCount != -1 && clickCount != aEvent.getClickCount()) {
-				aResult = false;
-			} else {
-				switch (type) {
-					case KeyEvent.KEY_PRESSED:
-						aResult = getAcceptsKeyPressed();
-						break;
+    public boolean acceptsEvent(PInputEvent aEvent, int type) {
+        boolean aResult = false;
+        int modifiers = 0;
 
-					case KeyEvent.KEY_RELEASED:
-						aResult = getAcceptsKeyReleased();
-						break;
+        if (!aEvent.isFocusEvent()) {
+            modifiers = aEvent.getModifiers();
+        }
 
-					case KeyEvent.KEY_TYPED:
-						aResult = getAcceptsKeyTyped();
-						break;
+        // TODO:  this really messes up the Eclipse formatter
+        if ((!aEvent.isHandled() || acceptsAlreadyHandledEvents) && (modifiers == 0 || // if
+                                                                                       // no
+                                                                                       // modifiers
+                                                                                       // then
+                                                                                       // ignore
+                                                                                       // modifier
+                                                                                       // constraints
+                                                                                       // ,
+                                                                                       // ELSE
+                (modifiers & andMask) == andMask && // must have all modifiers
+                                                    // from the AND mask and
+                        (modifiers & orMask) != 0 && // must have at least one
+                                                     // modifier from the OR
+                                                     // mask and
+                        (modifiers & notMask) == 0)) { // can't have any
+                                                       // modifiers from the NOT
+                                                       // mask
 
-					case MouseEvent.MOUSE_CLICKED:
-						aResult = getAcceptsMouseClicked();
-						break;
+            if (aEvent.isMouseEvent() && clickCount != -1 && clickCount != aEvent.getClickCount()) {
+                aResult = false;
+            }
+            else {
+                switch (type) {
+                    case KeyEvent.KEY_PRESSED:
+                        aResult = getAcceptsKeyPressed();
+                        break;
 
-					case MouseEvent.MOUSE_DRAGGED:
-						aResult = getAcceptsMouseDragged();
-						break;
+                    case KeyEvent.KEY_RELEASED:
+                        aResult = getAcceptsKeyReleased();
+                        break;
 
-					case MouseEvent.MOUSE_ENTERED:
-						aResult = getAcceptsMouseEntered();
-						break;
+                    case KeyEvent.KEY_TYPED:
+                        aResult = getAcceptsKeyTyped();
+                        break;
 
-					case MouseEvent.MOUSE_EXITED:
-						aResult = getAcceptsMouseExited();
-						break;
+                    case MouseEvent.MOUSE_CLICKED:
+                        aResult = getAcceptsMouseClicked();
+                        break;
 
-					case MouseEvent.MOUSE_MOVED:
-						aResult = getAcceptsMouseMoved();
-						break;
+                    case MouseEvent.MOUSE_DRAGGED:
+                        aResult = getAcceptsMouseDragged();
+                        break;
 
-					case MouseEvent.MOUSE_PRESSED:
-						aResult = getAcceptsMousePressed();
-						break;
+                    case MouseEvent.MOUSE_ENTERED:
+                        aResult = getAcceptsMouseEntered();
+                        break;
 
-					case MouseEvent.MOUSE_RELEASED:
-						aResult = getAcceptsMouseReleased();
-						break;
+                    case MouseEvent.MOUSE_EXITED:
+                        aResult = getAcceptsMouseExited();
+                        break;
 
-					case MouseWheelEvent.WHEEL_UNIT_SCROLL:
-					case MouseWheelEvent.WHEEL_BLOCK_SCROLL:
-						aResult = getAcceptsMouseWheelRotated();
-						break;
-						
-					case FocusEvent.FOCUS_GAINED:
-					case FocusEvent.FOCUS_LOST:
-						aResult = getAcceptsFocusEvents();
-						break;
+                    case MouseEvent.MOUSE_MOVED:
+                        aResult = getAcceptsMouseMoved();
+                        break;
 
-					default:
-						throw new RuntimeException("PInputEvent with bad ID");
-				}
-			}
-		}
+                    case MouseEvent.MOUSE_PRESSED:
+                        aResult = getAcceptsMousePressed();
+                        break;
 
-		if (aResult && getMarksAcceptedEventsAsHandled()) {
-			aEvent.setHandled(true);	 
-		}
+                    case MouseEvent.MOUSE_RELEASED:
+                        aResult = getAcceptsMouseReleased();
+                        break;
 
-		return aResult;
-	}
+                    case MouseWheelEvent.WHEEL_UNIT_SCROLL:
+                    case MouseWheelEvent.WHEEL_BLOCK_SCROLL:
+                        aResult = getAcceptsMouseWheelRotated();
+                        break;
 
-	public void acceptAllClickCounts() {
-		clickCount = -1;
-	}
+                    case FocusEvent.FOCUS_GAINED:
+                    case FocusEvent.FOCUS_LOST:
+                        aResult = getAcceptsFocusEvents();
+                        break;
 
-	public void acceptAllEventTypes() {
-		acceptsKeyPressed = true;
-		acceptsKeyReleased = true;
-		acceptsKeyTyped = true;
-		acceptsMouseClicked = true;
-		acceptsMouseDragged = true;
-		acceptsMouseEntered = true;
-		acceptsMouseExited = true;
-		acceptsMouseMoved = true;
-		acceptsMousePressed = true;
-		acceptsMouseReleased = true;
-		acceptsMouseWheelRotated = true;
-		acceptsFocusEvents = true;
-	}
+                    default:
+                        throw new RuntimeException("PInputEvent with bad ID");
+                }
+            }
+        }
 
-	public void acceptEverything() {
-		acceptAllEventTypes();
-		setAndMask(0);
-		setOrMask(ALL_MODIFIERS_MASK);
-		setNotMask(0);
-		acceptAllClickCounts();
-	}
+        if (aResult && getMarksAcceptedEventsAsHandled()) {
+            aEvent.setHandled(true);
+        }
 
-	public boolean getAcceptsKeyPressed() {
-		return acceptsKeyPressed;
-	}
+        return aResult;
+    }
 
-	public boolean getAcceptsKeyReleased() {
-		return acceptsKeyReleased;
-	}
+    public void acceptAllClickCounts() {
+        clickCount = -1;
+    }
 
-	public boolean getAcceptsKeyTyped() {
-		return acceptsKeyTyped;
-	}
+    public void acceptAllEventTypes() {
+        acceptsKeyPressed = true;
+        acceptsKeyReleased = true;
+        acceptsKeyTyped = true;
+        acceptsMouseClicked = true;
+        acceptsMouseDragged = true;
+        acceptsMouseEntered = true;
+        acceptsMouseExited = true;
+        acceptsMouseMoved = true;
+        acceptsMousePressed = true;
+        acceptsMouseReleased = true;
+        acceptsMouseWheelRotated = true;
+        acceptsFocusEvents = true;
+    }
 
-	public boolean getAcceptsMouseClicked() {
-		return acceptsMouseClicked;
-	}
+    public void acceptEverything() {
+        acceptAllEventTypes();
+        setAndMask(0);
+        setOrMask(ALL_MODIFIERS_MASK);
+        setNotMask(0);
+        acceptAllClickCounts();
+    }
 
-	public boolean getAcceptsMouseDragged() {
-		return acceptsMouseDragged;
-	}
+    public boolean getAcceptsKeyPressed() {
+        return acceptsKeyPressed;
+    }
 
-	public boolean getAcceptsMouseEntered() {
-		return acceptsMouseEntered;
-	}
+    public boolean getAcceptsKeyReleased() {
+        return acceptsKeyReleased;
+    }
 
-	public boolean getAcceptsMouseExited() {
-		return acceptsMouseExited;
-	}
+    public boolean getAcceptsKeyTyped() {
+        return acceptsKeyTyped;
+    }
 
-	public boolean getAcceptsMouseMoved() {
-		return acceptsMouseMoved;
-	}
+    public boolean getAcceptsMouseClicked() {
+        return acceptsMouseClicked;
+    }
 
-	public boolean getAcceptsMousePressed() {
-		return acceptsMousePressed;
-	}
+    public boolean getAcceptsMouseDragged() {
+        return acceptsMouseDragged;
+    }
 
-	public boolean getAcceptsMouseReleased() {
-		return acceptsMouseReleased;
-	}
+    public boolean getAcceptsMouseEntered() {
+        return acceptsMouseEntered;
+    }
 
-	public boolean getAcceptsMouseWheelRotated() {
-		return acceptsMouseWheelRotated;
-	}
+    public boolean getAcceptsMouseExited() {
+        return acceptsMouseExited;
+    }
 
-	public boolean getAcceptsFocusEvents() {
-		return acceptsFocusEvents;
-	}
+    public boolean getAcceptsMouseMoved() {
+        return acceptsMouseMoved;
+    }
 
-	public boolean getAcceptsAlreadyHandledEvents() {
-		return acceptsAlreadyHandledEvents;
-	}
+    public boolean getAcceptsMousePressed() {
+        return acceptsMousePressed;
+    }
 
-	public boolean getMarksAcceptedEventsAsHandled() {
-		return marksAcceptedEventsAsHandled;
-	}
+    public boolean getAcceptsMouseReleased() {
+        return acceptsMouseReleased;
+    }
 
-	public void rejectAllClickCounts() {
-		clickCount = Short.MAX_VALUE;
-	}
+    public boolean getAcceptsMouseWheelRotated() {
+        return acceptsMouseWheelRotated;
+    }
 
-	public void rejectAllEventTypes() {
-		acceptsKeyPressed = false;
-		acceptsKeyReleased = false;
-		acceptsKeyTyped = false;
-		acceptsMouseClicked = false;
-		acceptsMouseDragged = false;
-		acceptsMouseEntered = false;
-		acceptsMouseExited = false;
-		acceptsMouseMoved = false;
-		acceptsMousePressed = false;
-		acceptsMouseReleased = false;
-		acceptsMouseWheelRotated = false;
-		acceptsFocusEvents = false;
-	}
+    public boolean getAcceptsFocusEvents() {
+        return acceptsFocusEvents;
+    }
 
-	public void setAcceptClickCount(short aClickCount) {
-		clickCount = aClickCount;
-	}
+    public boolean getAcceptsAlreadyHandledEvents() {
+        return acceptsAlreadyHandledEvents;
+    }
 
-	public void setAcceptsKeyPressed(boolean aBoolean) {
-		acceptsKeyPressed = aBoolean;
-	}
+    public boolean getMarksAcceptedEventsAsHandled() {
+        return marksAcceptedEventsAsHandled;
+    }
 
-	public void setAcceptsKeyReleased(boolean aBoolean) {
-		acceptsKeyReleased = aBoolean;
-	}
+    public void rejectAllClickCounts() {
+        clickCount = Short.MAX_VALUE;
+    }
 
-	public void setAcceptsKeyTyped(boolean aBoolean) {
-		acceptsKeyTyped = aBoolean;
-	}
+    public void rejectAllEventTypes() {
+        acceptsKeyPressed = false;
+        acceptsKeyReleased = false;
+        acceptsKeyTyped = false;
+        acceptsMouseClicked = false;
+        acceptsMouseDragged = false;
+        acceptsMouseEntered = false;
+        acceptsMouseExited = false;
+        acceptsMouseMoved = false;
+        acceptsMousePressed = false;
+        acceptsMouseReleased = false;
+        acceptsMouseWheelRotated = false;
+        acceptsFocusEvents = false;
+    }
 
-	public void setAcceptsMouseClicked(boolean aBoolean) {
-		acceptsMouseClicked = aBoolean;
-	}
+    public void setAcceptClickCount(short aClickCount) {
+        clickCount = aClickCount;
+    }
 
-	public void setAcceptsMouseDragged(boolean aBoolean) {
-		acceptsMouseDragged = aBoolean;
-	}
+    public void setAcceptsKeyPressed(boolean aBoolean) {
+        acceptsKeyPressed = aBoolean;
+    }
 
-	public void setAcceptsMouseEntered(boolean aBoolean) {
-		acceptsMouseEntered = aBoolean;
-	}
+    public void setAcceptsKeyReleased(boolean aBoolean) {
+        acceptsKeyReleased = aBoolean;
+    }
 
-	public void setAcceptsMouseExited(boolean aBoolean) {
-		acceptsMouseExited = aBoolean;
-	}
+    public void setAcceptsKeyTyped(boolean aBoolean) {
+        acceptsKeyTyped = aBoolean;
+    }
 
-	public void setAcceptsMouseMoved(boolean aBoolean) {
-		acceptsMouseMoved = aBoolean;
-	}
+    public void setAcceptsMouseClicked(boolean aBoolean) {
+        acceptsMouseClicked = aBoolean;
+    }
 
-	public void setAcceptsMousePressed(boolean aBoolean) {
-		acceptsMousePressed = aBoolean;
-	}
+    public void setAcceptsMouseDragged(boolean aBoolean) {
+        acceptsMouseDragged = aBoolean;
+    }
 
-	public void setAcceptsMouseReleased(boolean aBoolean) {
-		acceptsMouseReleased = aBoolean;
-	}
+    public void setAcceptsMouseEntered(boolean aBoolean) {
+        acceptsMouseEntered = aBoolean;
+    }
 
-	public void setAcceptsMouseWheelRotated(boolean aBoolean) {
-		acceptsMouseWheelRotated = aBoolean;
-	}
-	
-	public void setAcceptsFocusEvents(boolean aBoolean) {
-		acceptsFocusEvents = aBoolean;
-	}
+    public void setAcceptsMouseExited(boolean aBoolean) {
+        acceptsMouseExited = aBoolean;
+    }
 
-	public void setAndMask(int aAndMask) {
-		andMask = aAndMask;
-	}
-	
-	public void setAcceptsAlreadyHandledEvents(boolean aBoolean) {
-		acceptsAlreadyHandledEvents = aBoolean;
-	}
+    public void setAcceptsMouseMoved(boolean aBoolean) {
+        acceptsMouseMoved = aBoolean;
+    }
 
-	public void setMarksAcceptedEventsAsHandled(boolean aBoolean) {
-		marksAcceptedEventsAsHandled = aBoolean;
-	}
+    public void setAcceptsMousePressed(boolean aBoolean) {
+        acceptsMousePressed = aBoolean;
+    }
 
-	public void setNotMask(int aNotMask) {
-		notMask = aNotMask;
-	}
+    public void setAcceptsMouseReleased(boolean aBoolean) {
+        acceptsMouseReleased = aBoolean;
+    }
 
-	public void setOrMask(int aOrMask) {
-		orMask = aOrMask;
-	}	
+    public void setAcceptsMouseWheelRotated(boolean aBoolean) {
+        acceptsMouseWheelRotated = aBoolean;
+    }
+
+    public void setAcceptsFocusEvents(boolean aBoolean) {
+        acceptsFocusEvents = aBoolean;
+    }
+
+    public void setAndMask(int aAndMask) {
+        andMask = aAndMask;
+    }
+
+    public void setAcceptsAlreadyHandledEvents(boolean aBoolean) {
+        acceptsAlreadyHandledEvents = aBoolean;
+    }
+
+    public void setMarksAcceptedEventsAsHandled(boolean aBoolean) {
+        marksAcceptedEventsAsHandled = aBoolean;
+    }
+
+    public void setNotMask(int aNotMask) {
+        notMask = aNotMask;
+    }
+
+    public void setOrMask(int aOrMask) {
+        orMask = aOrMask;
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/event/PInputEventListener.java b/core/src/main/java/edu/umd/cs/piccolo/event/PInputEventListener.java
index 26383cf..140cd81 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/event/PInputEventListener.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/event/PInputEventListener.java
@@ -33,17 +33,18 @@
 
 /**
  * PInputEventListener defines the most basic interface for objects that
- * want to listen to PNodes for input events. This interface is very simple so that
- * others may extend Piccolo's input management system. If you are just using Piccolo's
- * default input management system then you will most often use PBasicInputEventHandler
- * to register with a node for input events. 
+ * want to listen to PNodes for input events. This interface is very simple so
+ * that others may extend Piccolo's input management system. If you are just
+ * using Piccolo's default input management system then you will most often use
+ * PBasicInputEventHandler to register with a node for input events.
  *  
+ * 
  * @see PBasicInputEventHandler
  * @version 1.0
  * @author Jesse Grosjean
  */
 public interface PInputEventListener extends EventListener {
-	
-	public void processEvent(PInputEvent aEvent, int type);
+
+    public void processEvent(PInputEvent aEvent, int type);
 
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/event/PPanEventHandler.java b/core/src/main/java/edu/umd/cs/piccolo/event/PPanEventHandler.java
index aa4d7c1..0a50083 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/event/PPanEventHandler.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/event/PPanEventHandler.java
@@ -38,133 +38,143 @@
 import edu.umd.cs.piccolo.util.PDimension;
 
 /**
- * PPanEventHandler provides event handlers for basic panning
- * of the canvas view with the left mouse.	The interaction is that
- * clicking and dragging the mouse translates the view so that
- * the point on the surface stays under the mouse.
+ * PPanEventHandler provides event handlers for basic panning of the
+ * canvas view with the left mouse. The interaction is that clicking and
+ * dragging the mouse translates the view so that the point on the surface stays
+ * under the mouse.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PPanEventHandler extends PDragSequenceEventHandler {
-	
-	private boolean autopan;
-	private double minAutopanSpeed = 250;
-	private double maxAutopanSpeed = 750;
-	
-	public PPanEventHandler() {
-		super();
-		setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK));
-		setAutopan(true);
-	}
 
-	protected void drag(PInputEvent e) {
-		super.drag(e);
-		pan(e);
-	}
-	
-	protected void pan(PInputEvent e) {
-		PCamera c = e.getCamera();
-		Point2D l = e.getPosition();
-		
-		if (c.getViewBounds().contains(l)) {
-			PDimension d = e.getDelta();
-			c.translateView(d.getWidth(), d.getHeight());
-		}
-	}
-		
-	//****************************************************************
-	// Auto Pan
-	//****************************************************************
+    private boolean autopan;
+    private double minAutopanSpeed = 250;
+    private double maxAutopanSpeed = 750;
 
-	public void setAutopan(boolean autopan) {
-		this.autopan = autopan;
-	}
+    public PPanEventHandler() {
+        super();
+        setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK));
+        setAutopan(true);
+    }
 
-	public boolean getAutopan() {
-		return autopan;
-	}
-	
-	/**
-	 * Set the minAutoPan speed in pixels per second.
-	 * @param minAutopanSpeed
-	 */
-	public void setMinAutopanSpeed(double minAutopanSpeed) {
-		this.minAutopanSpeed = minAutopanSpeed;
-	}
-	
-	/**
-	 * Set the maxAutoPan speed in pixes per second.
-	 * @param maxAutopanSpeed
-	 */
-	public void setMaxAutopanSpeed(double maxAutopanSpeed) {
-		this.maxAutopanSpeed = maxAutopanSpeed;
-	}
-	
-	/**
-	 * Do auto panning	even when the mouse is not moving.
-	 */
-	protected void dragActivityStep(PInputEvent aEvent) {
-		if (!autopan) return;
-		
-		PCamera c = aEvent.getCamera();
-		PBounds b = c.getBoundsReference();
-		Point2D l = aEvent.getPositionRelativeTo(c);
-		int outcode = b.outcode(l);
-		PDimension delta = new PDimension();
-		
-		if ((outcode & Rectangle.OUT_TOP) != 0) {
-			delta.height = validatePanningSpeed(-1.0 - (0.5 * Math.abs(l.getY() - b.getY())));
-		} else if ((outcode & Rectangle.OUT_BOTTOM) != 0) {
-			delta.height = validatePanningSpeed(1.0 + (0.5 * Math.abs(l.getY() - (b.getY() + b.getHeight()))));
-		}
-		
-		if ((outcode & Rectangle.OUT_RIGHT) != 0) {
-			delta.width = validatePanningSpeed(1.0 + (0.5 * Math.abs(l.getX() - (b.getX() + b.getWidth()))));
-		} else if ((outcode & Rectangle.OUT_LEFT) != 0) {
-			delta.width = validatePanningSpeed(-1.0 - (0.5 * Math.abs(l.getX() - b.getX())));
-		}
-		
-		c.localToView(delta);		
-		
-		if (delta.width != 0 || delta.height != 0) {
-			c.translateView(delta.width, delta.height);
-		}
-	}
-	
-	protected double validatePanningSpeed(double delta) {
-		double minDelta = minAutopanSpeed / (1000 / getDragActivity().getStepRate());
-		double maxDelta = maxAutopanSpeed / (1000 / getDragActivity().getStepRate());
-		
-		boolean deltaNegative = delta < 0;
-		delta = Math.abs(delta);
-		if (delta < minDelta) delta = minDelta;
-		if (delta > maxDelta) delta = maxDelta;
-		if (deltaNegative) delta = -delta;
-		return delta;
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+    protected void drag(PInputEvent e) {
+        super.drag(e);
+        pan(e);
+    }
 
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be  
- * On a Mac with its single mouse button one may wish to change the
- * standard right mouse button zooming behavior. This can be easily done
- * with the PInputEventFilter. For example to zoom with button one and shift you
- * would do this:
+ * On a Mac with its single mouse button one may wish to change the standard
+ * right mouse button zooming behavior. This can be easily done with the
+ * PInputEventFilter. For example to zoom with button one and shift you would do
+ * this:
  *  
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PZoomEventHandler extends PDragSequenceEventHandler {
-	
-	private double minScale = 0;
-	private double maxScale = Double.MAX_VALUE;
-	private Point2D viewZoomPoint;
 
-	/**
-	 * Creates a new zoom handler.
-	 */
-	public PZoomEventHandler() {
-		super();
-		setEventFilter(new PInputEventFilter(InputEvent.BUTTON3_MASK));
-	}
+    private double minScale = 0;
+    private double maxScale = Double.MAX_VALUE;
+    private Point2D viewZoomPoint;
 
-	//****************************************************************
-	// Zooming
-	//****************************************************************
+    /**
+     * Creates a new zoom handler.
+     */
+    public PZoomEventHandler() {
+        super();
+        setEventFilter(new PInputEventFilter(InputEvent.BUTTON3_MASK));
+    }
 
-	/**
-	 * Returns the minimum view magnification factor that this event handler is bound by.
-	 * The default is 0.
-	 * @return the minimum camera view scale
-	 */
-	public double getMinScale() {
-		return minScale;
-	}
+    // ****************************************************************
+    // Zooming
+    // ****************************************************************
 
-	/**
-	 * Sets the minimum view magnification factor that this event handler is bound by.
-	 * The camera is left at its current scale even if  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PImage extends PNode {
-	
-	/** 
-	 * The property name that identifies a change of this node's image (see
-	 * {@link #getImage getImage}). Both old and new value will be set correctly
-	 * to Image objects in any property change event.
-	 */	
-	public static final String PROPERTY_IMAGE = "image";
+
+    /**
+     * The property name that identifies a change of this node's image (see
+     * {@link #getImage getImage}). Both old and new value will be set correctly
+     * to Image objects in any property change event.
+     */
+    public static final String PROPERTY_IMAGE = "image";
     public static final int PROPERTY_CODE_IMAGE = 1 << 15;
 
-	private transient Image image;
+    private transient Image image;
 
-	public PImage() {
-		super();
-	}
-	
-	/**
-	 * Construct a new PImage wrapping the given java.awt.Image.
-	 */
-	public PImage(Image newImage) {
-		this();
-		setImage(newImage);
-	}
+    public PImage() {
+        super();
+    }
 
-	/**
-	 * Construct a new PImage by loading the given fileName and wrapping the
-	 * resulting java.awt.Image.
-	 */
-	public PImage(String fileName) {
-		this(Toolkit.getDefaultToolkit().getImage(fileName));
-	}
-	
-	/**
-	 * Construct a new PImage by loading the given url and wrapping the
-	 * resulting java.awt.Image.  If the url is  
- * One option that applications have is to call  
- * This class also provides methods for constructing common shapes using a 
+ * This class also provides methods for constructing common shapes using a
  * general path.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PPath extends PNode {
-	
-	/** 
-	 * The property name that identifies a change of this node's stroke paint
-	 * (see {@link #getStrokePaint getStrokePaint}). Both old and new value will
-	 * be set correctly to Paint objects in any property change event.
-	 */
-	public static final String PROPERTY_STROKE_PAINT = "strokePaint";
+
+    /**
+     * The property name that identifies a change of this node's stroke paint
+     * (see {@link #getStrokePaint getStrokePaint}). Both old and new value will
+     * be set correctly to Paint objects in any property change event.
+     */
+    public static final String PROPERTY_STROKE_PAINT = "strokePaint";
     public static final int PROPERTY_CODE_STROKE_PAINT = 1 << 16;
 
-	/** 
-	 * The property name that identifies a change of this node's stroke (see
-	 * {@link #getStroke getStroke}). Both old and new value will be set
-	 * correctly to Stroke objects in any property change event.
-	 */
-	public static final String PROPERTY_STROKE = "stroke";
+    /**
+     * The property name that identifies a change of this node's stroke (see
+     * {@link #getStroke getStroke}). Both old and new value will be set
+     * correctly to Stroke objects in any property change event.
+     */
+    public static final String PROPERTY_STROKE = "stroke";
     public static final int PROPERTY_CODE_STROKE = 1 << 17;
 
-	/** 
-	 * The property name that identifies a change of this node's path (see
-	 * {@link #getPathReference getPathReference}).  In any property change
-	 * event the new value will be a reference to this node's path,  but old
-	 * value will always be null.
-	 */
-	public static final String PROPERTY_PATH = "path";
+    /**
+     * The property name that identifies a change of this node's path (see
+     * {@link #getPathReference getPathReference}). In any property change event
+     * the new value will be a reference to this node's path, but old value will
+     * always be null.
+     */
+    public static final String PROPERTY_PATH = "path";
     public static final int PROPERTY_CODE_PATH = 1 << 18;
-	
-	private static final Rectangle2D.Float TEMP_RECTANGLE = new Rectangle2D.Float();
-	private static final RoundRectangle2D.Float TEMP_ROUNDRECTANGLE = new RoundRectangle2D.Float();
-	private static final Ellipse2D.Float TEMP_ELLIPSE = new Ellipse2D.Float();
-	private static final PAffineTransform TEMP_TRANSFORM = new PAffineTransform();
-	private static final BasicStroke DEFAULT_STROKE = new BasicStroke(1.0f);
-	private static final Color DEFAULT_STROKE_PAINT = Color.black;
-	
-	private transient GeneralPath path;
-	private transient GeneralPath resizePath;
-	private transient Stroke stroke;
-	private transient boolean updatingBoundsFromPath;
-	private Paint strokePaint;
 
-	public static PPath createRectangle(float x, float y, float width, float height) {
-		TEMP_RECTANGLE.setFrame(x, y, width, height);
-		PPath result = new PPath(TEMP_RECTANGLE);
-		result.setPaint(Color.white);
-		return result;
-	}
+    private static final Rectangle2D.Float TEMP_RECTANGLE = new Rectangle2D.Float();
+    private static final RoundRectangle2D.Float TEMP_ROUNDRECTANGLE = new RoundRectangle2D.Float();
+    private static final Ellipse2D.Float TEMP_ELLIPSE = new Ellipse2D.Float();
+    private static final PAffineTransform TEMP_TRANSFORM = new PAffineTransform();
+    private static final BasicStroke DEFAULT_STROKE = new BasicStroke(1.0f);
+    private static final Color DEFAULT_STROKE_PAINT = Color.black;
 
-	public static PPath createRoundRectangle(float x, float y, float width, float height, float arcWidth, float arcHeight) {
-		TEMP_ROUNDRECTANGLE.setRoundRect(x, y, width, height, arcWidth, arcHeight);
-		PPath result = new PPath(TEMP_ROUNDRECTANGLE);
-		result.setPaint(Color.white);
-		return result;
-	}
+    private transient GeneralPath path;
+    private transient GeneralPath resizePath;
+    private transient Stroke stroke;
+    private transient boolean updatingBoundsFromPath;
+    private Paint strokePaint;
 
-	public static PPath createEllipse(float x, float y, float width, float height) {
-		TEMP_ELLIPSE.setFrame(x, y, width, height);
-		PPath result = new PPath(TEMP_ELLIPSE);
-		result.setPaint(Color.white);
-		return result;
-	}
-	
-	public static PPath createLine(float x1, float y1, float x2, float y2) {
-		PPath result = new PPath();
-		result.moveTo(x1, y1);
-		result.lineTo(x2, y2);
-		result.setPaint(Color.white);
-		return result;
-	}
-	
-	public static PPath createPolyline(Point2D[] points) {
-		PPath result = new PPath();
-		result.setPathToPolyline(points);
-		result.setPaint(Color.white);
-		return result;
-	}
+    public static PPath createRectangle(float x, float y, float width, float height) {
+        TEMP_RECTANGLE.setFrame(x, y, width, height);
+        PPath result = new PPath(TEMP_RECTANGLE);
+        result.setPaint(Color.white);
+        return result;
+    }
 
-	public static PPath createPolyline(float[] xp, float[] yp) {
-		PPath result = new PPath();
-		result.setPathToPolyline(xp, yp);
-		result.setPaint(Color.white);
-		return result;
-	}
-		
-	public PPath() {
-		strokePaint = DEFAULT_STROKE_PAINT;
-		stroke = DEFAULT_STROKE;
-		path = new GeneralPath();
-	}
+    public static PPath createRoundRectangle(float x, float y, float width, float height, float arcWidth,
+            float arcHeight) {
+        TEMP_ROUNDRECTANGLE.setRoundRect(x, y, width, height, arcWidth, arcHeight);
+        PPath result = new PPath(TEMP_ROUNDRECTANGLE);
+        result.setPaint(Color.white);
+        return result;
+    }
 
-	public PPath(Shape aShape) {
-		this(aShape, DEFAULT_STROKE);
-	}
+    public static PPath createEllipse(float x, float y, float width, float height) {
+        TEMP_ELLIPSE.setFrame(x, y, width, height);
+        PPath result = new PPath(TEMP_ELLIPSE);
+        result.setPaint(Color.white);
+        return result;
+    }
 
-	/**
-	 * Construct this path with the given shape and stroke.
-	 * This method may be used to optimize the creation of a large number of
-	 * PPaths. Normally PPaths have a default stroke of width one, but when a
-	 * path has a non null stroke it takes significantly longer to compute its
-	 * bounds. This method allows you to override that default stroke before the
-	 * bounds are ever calculated, so if you pass in a null stroke here you
-	 * won't ever have to pay that bounds calculation price if you don't need
-	 * to.
-	 */
-	public PPath(Shape aShape, Stroke aStroke) {
-		this();
-		stroke = aStroke;
-		if (aShape != null) append(aShape, false);
-	}
-	
-	//****************************************************************
-	// Stroke
-	//****************************************************************
-	
-	public Paint getStrokePaint() {
-		return strokePaint;
-	}
+    public static PPath createLine(float x1, float y1, float x2, float y2) {
+        PPath result = new PPath();
+        result.moveTo(x1, y1);
+        result.lineTo(x2, y2);
+        result.setPaint(Color.white);
+        return result;
+    }
 
-	public void setStrokePaint(Paint aPaint) {
-		Paint old = strokePaint;
-		strokePaint = aPaint;
-		invalidatePaint();
-		firePropertyChange(PROPERTY_CODE_STROKE_PAINT ,PROPERTY_STROKE_PAINT, old, strokePaint);
-	}
-	
-	public Stroke getStroke() {
-		return stroke;
-	}
+    public static PPath createPolyline(Point2D[] points) {
+        PPath result = new PPath();
+        result.setPathToPolyline(points);
+        result.setPaint(Color.white);
+        return result;
+    }
 
-	public void setStroke(Stroke aStroke) {
-		Stroke old = stroke;
-		stroke = aStroke;
-		updateBoundsFromPath();
-		invalidatePaint();
-		firePropertyChange(PROPERTY_CODE_STROKE ,PROPERTY_STROKE, old, stroke);
-	}
-		
-	//****************************************************************
-	// Bounds
-	//****************************************************************
-		
-	public void startResizeBounds() {
-		resizePath = new GeneralPath(path);
-	}
+    public static PPath createPolyline(float[] xp, float[] yp) {
+        PPath result = new PPath();
+        result.setPathToPolyline(xp, yp);
+        result.setPaint(Color.white);
+        return result;
+    }
 
-	public void endResizeBounds() {
-		resizePath = null;
-	}
-				
-	/**
-	 * Set the bounds of this path. This method works by scaling the path 
-	 * to fit into the specified bounds. This normally works well, but if 
-	 * the specified base bounds get too small then it is impossible to 
-	 * expand the path shape again since all its numbers have tended to zero, 
-	 * so application code may need to take this into consideration.
-	 */
-	protected void internalUpdateBounds(double x, double y, double width, double height) {
-		if (updatingBoundsFromPath) return;
-		if (path == null) return;
-		
-		if (resizePath != null) {
-			path.reset();
-			path.append(resizePath, false);
-		}
+    public PPath() {
+        strokePaint = DEFAULT_STROKE_PAINT;
+        stroke = DEFAULT_STROKE;
+        path = new GeneralPath();
+    }
 
-		Rectangle2D pathBounds = path.getBounds2D();
-		Rectangle2D pathStrokeBounds = getPathBoundsWithStroke();
-		double strokeOutset = Math.max(pathStrokeBounds.getWidth() - pathBounds.getWidth(), 
-										pathStrokeBounds.getHeight() - pathBounds.getHeight());
-		
-		x += strokeOutset / 2;
-		y += strokeOutset / 2;
-		width -= strokeOutset;
-		height -= strokeOutset;
-		
-		double scaleX = (width == 0 || pathBounds.getWidth() == 0) ? 1 : width / pathBounds.getWidth();
-		double scaleY = (height == 0 || pathBounds.getHeight() == 0) ? 1 : height / pathBounds.getHeight();
-		
-		TEMP_TRANSFORM.setToIdentity();
-		TEMP_TRANSFORM.translate(x, y);
-		TEMP_TRANSFORM.scale(scaleX, scaleY);
-		TEMP_TRANSFORM.translate(-pathBounds.getX(), -pathBounds.getY());
-		
-		path.transform(TEMP_TRANSFORM);
-	}
-	
-	public boolean intersects(Rectangle2D aBounds) {
-		if (super.intersects(aBounds)) {
-			if (getPaint() != null && path.intersects(aBounds)) {
-				return true;
-			} else if (stroke != null && strokePaint != null) {
-				return stroke.createStrokedShape(path).intersects(aBounds);
-			}
-		}
-		return false;
-	}
-			
-	public Rectangle2D getPathBoundsWithStroke() {
-		if (stroke != null) {
-			return stroke.createStrokedShape(path).getBounds2D();
-		} else {
-			return path.getBounds2D();
-		}
-	}
-			
-	public void updateBoundsFromPath() {
-		updatingBoundsFromPath = true;
-		if (path == null) {
-			resetBounds();
-		} else {
-			Rectangle2D b = getPathBoundsWithStroke();
-			setBounds(b.getX(), b.getY(), b.getWidth(), b.getHeight());
-		}
-		updatingBoundsFromPath = false;
-	}
-	
-	//****************************************************************
-	// Painting
-	//****************************************************************
-	
-	protected void paint(PPaintContext paintContext) {
-		Paint p = getPaint();
-		Graphics2D g2 = paintContext.getGraphics();
-		
-		if (p != null) {
-			g2.setPaint(p);
-			g2.fill(path);
-		}
+    public PPath(Shape aShape) {
+        this(aShape, DEFAULT_STROKE);
+    }
 
-		if (stroke != null && strokePaint != null) {
-			g2.setPaint(strokePaint);
-			g2.setStroke(stroke);
-			g2.draw(path);
-		}		
-	}	
-		
-	//****************************************************************
-	// Path Support set java.awt.GeneralPath documentation for more
-	// information on using these methods.
-	//****************************************************************
+    /**
+     * Construct this path with the given shape and stroke. This method may be
+     * used to optimize the creation of a large number of PPaths. Normally
+     * PPaths have a default stroke of width one, but when a path has a non null
+     * stroke it takes significantly longer to compute its bounds. This method
+     * allows you to override that default stroke before the bounds are ever
+     * calculated, so if you pass in a null stroke here you won't ever have to
+     * pay that bounds calculation price if you don't need to.
+     */
+    public PPath(Shape aShape, Stroke aStroke) {
+        this();
+        stroke = aStroke;
+        if (aShape != null)
+            append(aShape, false);
+    }
 
-	public GeneralPath getPathReference() {
-		return path;
-	}
-	
-	public void moveTo(float x, float y) {
-		path.moveTo(x, y);
-		firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
-		updateBoundsFromPath();
-		invalidatePaint();
-	}
-	
-	public void lineTo(float x, float y) {
-		path.lineTo(x, y);
-		firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
-		updateBoundsFromPath();
-		invalidatePaint();
-	}
+    // ****************************************************************
+    // Stroke
+    // ****************************************************************
 
-	public void quadTo(float x1, float y1, float x2, float y2) {
-		path.quadTo(x1, y1, x2, y2);
-		firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
-		updateBoundsFromPath();
-		invalidatePaint();
-	}
+    public Paint getStrokePaint() {
+        return strokePaint;
+    }
 
-	public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) {
-		path.curveTo(x1, y1, x2, y2, x3, y3);
-		firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
-		updateBoundsFromPath();
-		invalidatePaint();
-	}
-	
-	public void append(Shape aShape, boolean connect) {
-		path.append(aShape, connect);
-		firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
-		updateBoundsFromPath();
-		invalidatePaint();
-	}
-	
-	public void setPathTo(Shape aShape) {
-		path.reset();
-		append(aShape, false);
-	}
+    public void setStrokePaint(Paint aPaint) {
+        Paint old = strokePaint;
+        strokePaint = aPaint;
+        invalidatePaint();
+        firePropertyChange(PROPERTY_CODE_STROKE_PAINT, PROPERTY_STROKE_PAINT, old, strokePaint);
+    }
 
-	public void setPathToRectangle(float x, float y, float width, float height) {
-		TEMP_RECTANGLE.setFrame(x, y, width, height);
-		setPathTo(TEMP_RECTANGLE);
-	}
+    public Stroke getStroke() {
+        return stroke;
+    }
 
-	public void setPathToEllipse(float x, float y, float width, float height) {
-		TEMP_ELLIPSE.setFrame(x, y, width, height);
-		setPathTo(TEMP_ELLIPSE);
-	}
+    public void setStroke(Stroke aStroke) {
+        Stroke old = stroke;
+        stroke = aStroke;
+        updateBoundsFromPath();
+        invalidatePaint();
+        firePropertyChange(PROPERTY_CODE_STROKE, PROPERTY_STROKE, old, stroke);
+    }
 
-	public void setPathToPolyline(Point2D[] points) {
-		path.reset();
-		path.moveTo((float)points[0].getX(), (float)points[0].getY());
-		for (int i = 1; i < points.length; i++) {
-			path.lineTo((float)points[i].getX(), (float)points[i].getY());
-		}
-		firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
-		updateBoundsFromPath();
-		invalidatePaint();		
-	}
+    // ****************************************************************
+    // Bounds
+    // ****************************************************************
 
-	public void setPathToPolyline(float[] xp, float[] yp) {
-		path.reset();
-		path.moveTo(xp[0], yp[0]);
-		for (int i = 1; i < xp.length; i++) {
-			path.lineTo(xp[i], yp[i]);
-		}
-		firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
-		updateBoundsFromPath();
-		invalidatePaint();
-	}
-	
-	public void closePath() {
-		path.closePath();
-		firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
-		updateBoundsFromPath();
-		invalidatePaint();
-	}
-	
-	public void reset() {
-		path.reset();
-		firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
-		updateBoundsFromPath();
-		invalidatePaint();
-	}
-	
-	//****************************************************************
-	// Serialization
-	//****************************************************************
-	
-	private void writeObject(ObjectOutputStream out) throws IOException {
-		out.defaultWriteObject();
-		PUtil.writeStroke(stroke, out); 	   
-		PUtil.writePath(path, out); 	   
-	}
+    public void startResizeBounds() {
+        resizePath = new GeneralPath(path);
+    }
 
-	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
-		in.defaultReadObject();		
-		stroke = PUtil.readStroke(in);
-		path = PUtil.readPath(in);		
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
-	
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be  
+ * 
  * @version 1.1
  * @author Jesse Grosjean
  */
 public class PText extends PNode {
-	
-	/** 
-	 * The property name that identifies a change of this node's text (see
-	 * {@link #getText getText}). Both old and new value will be set in any
-	 * property change event. 
-	 */
-	public static final String PROPERTY_TEXT = "text";
+
+    /**
+     * The property name that identifies a change of this node's text (see
+     * {@link #getText getText}). Both old and new value will be set in any
+     * property change event.
+     */
+    public static final String PROPERTY_TEXT = "text";
     public static final int PROPERTY_CODE_TEXT = 1 << 19;
-	
-	/** 
-	 * The property name that identifies a change of this node's font (see
-	 * {@link #getFont getFont}).  Both old and new value will be set in any
-	 * property change event.
-	 */
-	public static final String PROPERTY_FONT = "font";
+
+    /**
+     * The property name that identifies a change of this node's font (see
+     * {@link #getFont getFont}). Both old and new value will be set in any
+     * property change event.
+     */
+    public static final String PROPERTY_FONT = "font";
     public static final int PROPERTY_CODE_FONT = 1 << 20;
 
-	public static Font DEFAULT_FONT = new Font("Helvetica", Font.PLAIN, 12);
-	public static double DEFAULT_GREEK_THRESHOLD = 5.5;
-	
-	private String text;
-	private Paint textPaint;
-	private Font font;
-	protected double greekThreshold = DEFAULT_GREEK_THRESHOLD;
-	private float justification = javax.swing.JLabel.LEFT_ALIGNMENT;
-	private boolean constrainHeightToTextHeight = true;
-	private boolean constrainWidthToTextWidth = true;
-	private transient TextLayout[] lines;
-    
-	public PText() {
-		super();
-		setTextPaint(Color.BLACK);
-	}
+    public static Font DEFAULT_FONT = new Font("Helvetica", Font.PLAIN, 12);
+    public static double DEFAULT_GREEK_THRESHOLD = 5.5;
 
-	public PText(String aText) {
-		this();
-		setText(aText);
-	}
-	
-	/**
-	 * Return the justificaiton of the text in the bounds.
-	 * @return float
-	 */
-	public float getJustification() {
-		return justification;
-	}
+    private String text;
+    private Paint textPaint;
+    private Font font;
+    protected double greekThreshold = DEFAULT_GREEK_THRESHOLD;
+    private float justification = javax.swing.JLabel.LEFT_ALIGNMENT;
+    private boolean constrainHeightToTextHeight = true;
+    private boolean constrainWidthToTextWidth = true;
+    private transient TextLayout[] lines;
 
-	/**
+    public PText() {
+        super();
+        setTextPaint(Color.BLACK);
+    }
+
+    public PText(String aText) {
+        this();
+        setText(aText);
+    }
+
+    /**
+     * Return the justificaiton of the text in the bounds.
+     * 
+     * @return float
+     */
+    public float getJustification() {
+        return justification;
+    }
+
+    /**
      * Sets the justificaiton of the text in the bounds.
-	 * @param just
-	 */	
-	public void setJustification(float just) {
-		justification = just;
-		recomputeLayout();
-	}
+     * 
+     * @param just
+     */
+    public void setJustification(float just) {
+        justification = just;
+        recomputeLayout();
+    }
 
-	/**
-	 * Get the paint used to paint this nodes text.
-	 * @return Paint
-	 */
-	public Paint getTextPaint() {
-		return textPaint;
-	}
+    /**
+     * Get the paint used to paint this nodes text.
+     * 
+     * @return Paint
+     */
+    public Paint getTextPaint() {
+        return textPaint;
+    }
 
-	/**
-	 * Set the paint used to paint this node's text background.
-	 * @param textPaint
-	 */		
-	public void setTextPaint(Paint textPaint) {
-		this.textPaint = textPaint;
-		invalidatePaint();
-	}
+    /**
+     * Set the paint used to paint this node's text background.
+     * 
+     * @param textPaint
+     */
+    public void setTextPaint(Paint textPaint) {
+        this.textPaint = textPaint;
+        invalidatePaint();
+    }
 
     public boolean isConstrainWidthToTextWidth() {
         return constrainWidthToTextWidth;
     }
 
-	/**
-	 * Controls whether this node changes its width to fit the width 
-	 * of its text. If flag is true it does; if flag is false it doesn't
-	 */
-	public void setConstrainWidthToTextWidth(boolean constrainWidthToTextWidth) {
-		this.constrainWidthToTextWidth = constrainWidthToTextWidth;
-		recomputeLayout();
-	}
+    /**
+     * Controls whether this node changes its width to fit the width of its
+     * text. If flag is true it does; if flag is false it doesn't
+     */
+    public void setConstrainWidthToTextWidth(boolean constrainWidthToTextWidth) {
+        this.constrainWidthToTextWidth = constrainWidthToTextWidth;
+        recomputeLayout();
+    }
 
     public boolean isConstrainHeightToTextHeight() {
         return constrainHeightToTextHeight;
     }
 
-	/**
-	 * Controls whether this node changes its height to fit the height 
-	 * of its text. If flag is true it does; if flag is false it doesn't
-	 */
-	public void setConstrainHeightToTextHeight(boolean constrainHeightToTextHeight) {
-		this.constrainHeightToTextHeight = constrainHeightToTextHeight;
-		recomputeLayout();
-	}
+    /**
+     * Controls whether this node changes its height to fit the height of its
+     * text. If flag is true it does; if flag is false it doesn't
+     */
+    public void setConstrainHeightToTextHeight(boolean constrainHeightToTextHeight) {
+        this.constrainHeightToTextHeight = constrainHeightToTextHeight;
+        recomputeLayout();
+    }
 
-	/**
-	 * Returns the current greek threshold. When the screen font size will be below
-	 * this threshold the text is rendered as 'greek' instead of drawing the text
-	 * glyphs.
-	 */
-	public double getGreekThreshold() {
-		return greekThreshold;
-	}
+    /**
+     * Returns the current greek threshold. When the screen font size will be
+     * below this threshold the text is rendered as 'greek' instead of drawing
+     * the text glyphs.
+     */
+    public double getGreekThreshold() {
+        return greekThreshold;
+    }
 
-	/**
-	 * Sets the current greek threshold. When the screen font size will be below
-	 * this threshold the text is rendered as 'greek' instead of drawing the text
-	 * glyphs.
-	 * 
-	 * @param threshold minimum screen font size.
-	 */
-	public void setGreekThreshold(double threshold) {
-		greekThreshold = threshold;
-		invalidatePaint();
-	}
-		
-	public String getText() {
-		return text;
-	}
+    /**
+     * Sets the current greek threshold. When the screen font size will be below
+     * this threshold the text is rendered as 'greek' instead of drawing the
+     * text glyphs.
+     * 
+     * @param threshold minimum screen font size.
+     */
+    public void setGreekThreshold(double threshold) {
+        greekThreshold = threshold;
+        invalidatePaint();
+    }
 
-	/**
-	 * Set the text for this node. The text will be broken up into multiple
-	 * lines based on the size of the text and the bounds width of this node.
-	 */
-	public void setText(String aText) {
-		String old = text;
-		text = aText;
-		lines = null;
-		recomputeLayout();
-		invalidatePaint();
-		firePropertyChange(PROPERTY_CODE_TEXT, PROPERTY_TEXT, old, text);
-	}
-	
-	/**
-	 * Returns the font of this PText.
-	 * @return the font of this PText.
-	 */ 
-	public Font getFont() {
-		if (font == null) {
-			font = DEFAULT_FONT;
-		}
-		return font;
-	}
-	
-	/**
-	 * Set the font of this PText. Note that in Piccolo if you want to change
-	 * the size of a text object it's often a better idea to scale the PText
-	 * node instead of changing the font size to get that same effect. Using
-	 * very large font sizes can slow performance.
-	 */
-	public void setFont(Font aFont) {
-		Font old = font;
-		font = aFont;
-		lines = null;
-		recomputeLayout();
-		invalidatePaint();
-		firePropertyChange(PROPERTY_CODE_FONT, PROPERTY_FONT, old, font);
-	}
+    public String getText() {
+        return text;
+    }
 
-	private static final TextLayout[] EMPTY_TEXT_LAYOUT_ARRAY = new TextLayout[0];
-		
-	/**
-	 * Compute the bounds of the text wrapped by this node. The text layout
-	 * is wrapped based on the bounds of this node.
-	 */
-	public void recomputeLayout() {
-		ArrayList linesList = new ArrayList();
-		double textWidth = 0;
-		double textHeight = 0;
+    /**
+     * Set the text for this node. The text will be broken up into multiple
+     * lines based on the size of the text and the bounds width of this node.
+     */
+    public void setText(String aText) {
+        String old = text;
+        text = aText;
+        lines = null;
+        recomputeLayout();
+        invalidatePaint();
+        firePropertyChange(PROPERTY_CODE_TEXT, PROPERTY_TEXT, old, text);
+    }
 
-		if (text != null && text.length() > 0) {		
-			AttributedString atString = new AttributedString(text);
-			atString.addAttribute(TextAttribute.FONT, getFont());
-			AttributedCharacterIterator itr = atString.getIterator();
-			LineBreakMeasurer measurer = new LineBreakMeasurer(itr, PPaintContext.RENDER_QUALITY_HIGH_FRC);
-			float availableWidth = constrainWidthToTextWidth ? Float.MAX_VALUE : (float) getWidth();
-			
-			int nextLineBreakOffset = text.indexOf('\n');
-			if (nextLineBreakOffset == -1) {
-				nextLineBreakOffset = Integer.MAX_VALUE;
-			} else {
-				nextLineBreakOffset++;
-			}
-			
-			while (measurer.getPosition() < itr.getEndIndex()) {
-				TextLayout aTextLayout = computeNextLayout(measurer, availableWidth, nextLineBreakOffset);
+    /**
+     * Returns the font of this PText.
+     * 
+     * @return the font of this PText.
+     */
+    public Font getFont() {
+        if (font == null) {
+            font = DEFAULT_FONT;
+        }
+        return font;
+    }
 
-				if (nextLineBreakOffset == measurer.getPosition()) {
-					nextLineBreakOffset = text.indexOf('\n', measurer.getPosition());
-					if (nextLineBreakOffset == -1) {
-						nextLineBreakOffset = Integer.MAX_VALUE;
-					} else {
-						nextLineBreakOffset++;
-					}
-				}
-								
-				linesList.add(aTextLayout);
-				textHeight += aTextLayout.getAscent();
-				textHeight += aTextLayout.getDescent() + aTextLayout.getLeading();
-				textWidth = Math.max(textWidth, aTextLayout.getAdvance());
-			}
-		}
-				
-		lines = (TextLayout[]) linesList.toArray(EMPTY_TEXT_LAYOUT_ARRAY);
-					
-		if (constrainWidthToTextWidth || constrainHeightToTextHeight) {
-			double newWidth = getWidth();
-			double newHeight = getHeight();
-			
-			if (constrainWidthToTextWidth) {
-				newWidth = textWidth;
-			}
-			
-			if (constrainHeightToTextHeight) {
-				newHeight = textHeight;
-			}
-	
-			super.setBounds(getX(), getY(), newWidth, newHeight);
-		}	
-	}
-	
-	// provided in case someone needs to override the way that lines are wrapped.
-	protected TextLayout computeNextLayout(LineBreakMeasurer measurer, float availibleWidth, int nextLineBreakOffset) {
-		return measurer.nextLayout(availibleWidth, nextLineBreakOffset, false);
-	}
-		
-	protected void paint(PPaintContext paintContext) {		
-		super.paint(paintContext);
-		
-		float screenFontSize = getFont().getSize() * (float) paintContext.getScale();
-		if (textPaint != null && screenFontSize > greekThreshold) {
-			float x = (float) getX();
-			float y = (float) getY();
-			float bottomY = (float) getHeight() + y;
-			
-			Graphics2D g2 = paintContext.getGraphics();
-			
-			if (lines == null) {
-				recomputeLayout();
-				repaint();
-				return;
-			}
+    /**
+     * Set the font of this PText. Note that in Piccolo if you want to change
+     * the size of a text object it's often a better idea to scale the PText
+     * node instead of changing the font size to get that same effect. Using
+     * very large font sizes can slow performance.
+     */
+    public void setFont(Font aFont) {
+        Font old = font;
+        font = aFont;
+        lines = null;
+        recomputeLayout();
+        invalidatePaint();
+        firePropertyChange(PROPERTY_CODE_FONT, PROPERTY_FONT, old, font);
+    }
 
-			g2.setPaint(textPaint);
-			
-			for (int i = 0; i < lines.length; i++) {
+    private static final TextLayout[] EMPTY_TEXT_LAYOUT_ARRAY = new TextLayout[0];
+
+    /**
+     * Compute the bounds of the text wrapped by this node. The text layout is
+     * wrapped based on the bounds of this node.
+     */
+    public void recomputeLayout() {
+        ArrayList linesList = new ArrayList();
+        double textWidth = 0;
+        double textHeight = 0;
+
+        if (text != null && text.length() > 0) {
+            AttributedString atString = new AttributedString(text);
+            atString.addAttribute(TextAttribute.FONT, getFont());
+            AttributedCharacterIterator itr = atString.getIterator();
+            LineBreakMeasurer measurer = new LineBreakMeasurer(itr, PPaintContext.RENDER_QUALITY_HIGH_FRC);
+            float availableWidth = constrainWidthToTextWidth ? Float.MAX_VALUE : (float) getWidth();
+
+            int nextLineBreakOffset = text.indexOf('\n');
+            if (nextLineBreakOffset == -1) {
+                nextLineBreakOffset = Integer.MAX_VALUE;
+            }
+            else {
+                nextLineBreakOffset++;
+            }
+
+            while (measurer.getPosition() < itr.getEndIndex()) {
+                TextLayout aTextLayout = computeNextLayout(measurer, availableWidth, nextLineBreakOffset);
+
+                if (nextLineBreakOffset == measurer.getPosition()) {
+                    nextLineBreakOffset = text.indexOf('\n', measurer.getPosition());
+                    if (nextLineBreakOffset == -1) {
+                        nextLineBreakOffset = Integer.MAX_VALUE;
+                    }
+                    else {
+                        nextLineBreakOffset++;
+                    }
+                }
+
+                linesList.add(aTextLayout);
+                textHeight += aTextLayout.getAscent();
+                textHeight += aTextLayout.getDescent() + aTextLayout.getLeading();
+                textWidth = Math.max(textWidth, aTextLayout.getAdvance());
+            }
+        }
+
+        lines = (TextLayout[]) linesList.toArray(EMPTY_TEXT_LAYOUT_ARRAY);
+
+        if (constrainWidthToTextWidth || constrainHeightToTextHeight) {
+            double newWidth = getWidth();
+            double newHeight = getHeight();
+
+            if (constrainWidthToTextWidth) {
+                newWidth = textWidth;
+            }
+
+            if (constrainHeightToTextHeight) {
+                newHeight = textHeight;
+            }
+
+            super.setBounds(getX(), getY(), newWidth, newHeight);
+        }
+    }
+
+    // provided in case someone needs to override the way that lines are
+    // wrapped.
+    protected TextLayout computeNextLayout(LineBreakMeasurer measurer, float availibleWidth, int nextLineBreakOffset) {
+        return measurer.nextLayout(availibleWidth, nextLineBreakOffset, false);
+    }
+
+    protected void paint(PPaintContext paintContext) {
+        super.paint(paintContext);
+
+        float screenFontSize = getFont().getSize() * (float) paintContext.getScale();
+        if (textPaint != null && screenFontSize > greekThreshold) {
+            float x = (float) getX();
+            float y = (float) getY();
+            float bottomY = (float) getHeight() + y;
+
+            Graphics2D g2 = paintContext.getGraphics();
+
+            if (lines == null) {
+                recomputeLayout();
+                repaint();
+                return;
+            }
+
+            g2.setPaint(textPaint);
+
+            for (int i = 0; i < lines.length; i++) {
                 TextLayout tl = lines[i];
-				y += tl.getAscent();
-				
-				if (bottomY < y) {
-					return;
-				}
-                
+                y += tl.getAscent();
+
+                if (bottomY < y) {
+                    return;
+                }
+
                 float offset = (float) (getWidth() - tl.getAdvance()) * justification;
                 tl.draw(g2, x + offset, y);
-	
-				y += tl.getDescent() + tl.getLeading();
-			}
-		}
-	}
-	
-	protected void internalUpdateBounds(double x, double y, double width, double height) {
-		recomputeLayout();
-	}
 
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
-	
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PAffineTransform extends AffineTransform {
 
-	private static double[] PTS1 = new double[8];
-	private static double[] PTS2 = new double[8];
+    private static double[] PTS1 = new double[8];
+    private static double[] PTS2 = new double[8];
 
-	public PAffineTransform() {
-		super();
-	}
+    public PAffineTransform() {
+        super();
+    }
 
-	public PAffineTransform(double[] flatmatrix) {
-		super(flatmatrix);
-	}
+    public PAffineTransform(double[] flatmatrix) {
+        super(flatmatrix);
+    }
 
-	public PAffineTransform(float[] flatmatrix) {
-		super(flatmatrix);
-	}
+    public PAffineTransform(float[] flatmatrix) {
+        super(flatmatrix);
+    }
 
-	public PAffineTransform(double m00, double m10, double m01, double m11, double m02, double m12) {
-		super(m00, m10, m01, m11, m02, m12);
-	}
+    public PAffineTransform(double m00, double m10, double m01, double m11, double m02, double m12) {
+        super(m00, m10, m01, m11, m02, m12);
+    }
 
-	public PAffineTransform(float m00, float m10, float m01, float m11, float m02, float m12) {
-		super(m00, m10, m01, m11, m02, m12);
-	}
+    public PAffineTransform(float m00, float m10, float m01, float m11, float m02, float m12) {
+        super(m00, m10, m01, m11, m02, m12);
+    }
 
-	public PAffineTransform(AffineTransform tx) {
-		super(tx);
-	}
+    public PAffineTransform(AffineTransform tx) {
+        super(tx);
+    }
 
-	public void scaleAboutPoint(double scale, double x, double y) {
-		translate(x, y);
-		scale(scale, scale);
-		translate(-x, -y);
-	}
+    public void scaleAboutPoint(double scale, double x, double y) {
+        translate(x, y);
+        scale(scale, scale);
+        translate(-x, -y);
+    }
 
-	public double getScale() {
-		PTS1[0] = 0;//x1
-		PTS1[1] = 0;//y1
-		PTS1[2] = 1;//x2
-		PTS1[3] = 0;//y2
-		transform(PTS1, 0, PTS2, 0, 2);
-		return Point2D.distance(PTS2[0], PTS2[1], PTS2[2], PTS2[3]);
-	}
-	
-	public void setScale(double scale) {
-		if (scale == 0) throw new RuntimeException("Can't set scale to 0");
-		scaleAboutPoint(scale / getScale(), 0, 0);
-	}
+    public double getScale() {
+        PTS1[0] = 0;// x1
+        PTS1[1] = 0;// y1
+        PTS1[2] = 1;// x2
+        PTS1[3] = 0;// y2
+        transform(PTS1, 0, PTS2, 0, 2);
+        return Point2D.distance(PTS2[0], PTS2[1], PTS2[2], PTS2[3]);
+    }
 
-	public void setOffset(double tx, double ty) {
-		setTransform(getScaleX(), getShearY(), getShearX(), getScaleY(), tx, ty);
-	}
+    public void setScale(double scale) {
+        if (scale == 0)
+            throw new RuntimeException("Can't set scale to 0");
+        scaleAboutPoint(scale / getScale(), 0, 0);
+    }
 
-	/**
-	 * Returns the rotation applied to this affine transform in radians. The
-	 * value returned will be between 0 and 2pi.
-	 * 
-	 * @return rotation in radians
-	 */
-	public double getRotation() {				
-		PTS1[0] = 0;//x1
-		PTS1[1] = 0;//y1
-		PTS1[2] = 1;//x2
-		PTS1[3] = 0;//y2
+    public void setOffset(double tx, double ty) {
+        setTransform(getScaleX(), getShearY(), getShearX(), getScaleY(), tx, ty);
+    }
 
-		transform(PTS1, 0, PTS2, 0, 2);
+    /**
+     * Returns the rotation applied to this affine transform in radians. The
+     * value returned will be between 0 and 2pi.
+     * 
+     * @return rotation in radians
+     */
+    public double getRotation() {
+        PTS1[0] = 0;// x1
+        PTS1[1] = 0;// y1
+        PTS1[2] = 1;// x2
+        PTS1[3] = 0;// y2
 
-		double dy = Math.abs(PTS2[3] - PTS2[1]);
-		double l = Point2D.distance(PTS2[0], PTS2[1], PTS2[2], PTS2[3]);
-		double rotation = Math.asin(dy / l);		
-		
-		// correct for quadrant
-		if (PTS2[3] - PTS2[1] > 0) {
-			if (PTS2[2] - PTS2[0] < 0) {
-				rotation = Math.PI - rotation;
-			}
-		} else {
-			if (PTS2[2] - PTS2[0] > 0) {
-				rotation = 2 * Math.PI - rotation;
-			} else {
-				rotation = rotation + Math.PI;
-			}
-		}
+        transform(PTS1, 0, PTS2, 0, 2);
 
-		return rotation;
-	}
+        double dy = Math.abs(PTS2[3] - PTS2[1]);
+        double l = Point2D.distance(PTS2[0], PTS2[1], PTS2[2], PTS2[3]);
+        double rotation = Math.asin(dy / l);
 
-	/**
-	 * Set rotation in radians.
-	 */
-	public void setRotation(double theta) {
-		rotate(theta - getRotation());
-	}
+        // correct for quadrant
+        if (PTS2[3] - PTS2[1] > 0) {
+            if (PTS2[2] - PTS2[0] < 0) {
+                rotation = Math.PI - rotation;
+            }
+        }
+        else {
+            if (PTS2[2] - PTS2[0] > 0) {
+                rotation = 2 * Math.PI - rotation;
+            }
+            else {
+                rotation = rotation + Math.PI;
+            }
+        }
 
-	public Dimension2D transform(Dimension2D dimSrc, Dimension2D dimDst) {
-		if (dimDst == null) {
-			dimDst = (Dimension2D) dimSrc.clone();
-		}
+        return rotation;
+    }
 
-		PTS1[0] = dimSrc.getWidth();
-		PTS1[1] = dimSrc.getHeight();
-		deltaTransform(PTS1, 0, PTS2, 0, 1);
-		dimDst.setSize(PTS2[0], PTS2[1]);
-		return dimDst;
-	}
+    /**
+     * Set rotation in radians.
+     */
+    public void setRotation(double theta) {
+        rotate(theta - getRotation());
+    }
 
-	public Dimension2D inverseTransform(Dimension2D dimSrc, Dimension2D dimDst) {
-		if (dimDst == null) {
-			dimDst = (Dimension2D) dimSrc.clone();
-		}
+    public Dimension2D transform(Dimension2D dimSrc, Dimension2D dimDst) {
+        if (dimDst == null) {
+            dimDst = (Dimension2D) dimSrc.clone();
+        }
 
-		double width = dimSrc.getWidth();
-		double height = dimSrc.getHeight();
-		double m00 = getScaleX();
-		double m11 = getScaleY();
-		double m01 = getShearX();
-		double m10 = getShearY();
-		double det = m00 * m11 - m01 * m10;
-		
-		try {
-			if (Math.abs(det) <= Double.MIN_VALUE) {
-				throw new NoninvertibleTransformException("Determinant is "+ det);
-			}
-			dimDst.setSize((width * m11 - height * m01) / det, (height * m00 - width * m10) / det);
-		} catch (NoninvertibleTransformException e) {
-			e.printStackTrace();
-		}
+        PTS1[0] = dimSrc.getWidth();
+        PTS1[1] = dimSrc.getHeight();
+        deltaTransform(PTS1, 0, PTS2, 0, 1);
+        dimDst.setSize(PTS2[0], PTS2[1]);
+        return dimDst;
+    }
 
-		return dimDst;
-	}
-								 
-	public Rectangle2D transform(Rectangle2D rectSrc, Rectangle2D rectDst) {
-		if (rectDst == null) {
-			rectDst = (Rectangle2D) rectSrc.clone();
-		}
-		
-		if (rectSrc.isEmpty()) {
-			rectDst.setRect(rectSrc);
-			if (rectDst instanceof PBounds) {
-				((PBounds)rectDst).reset();
-			}
-			return rectDst;
-		}
+    public Dimension2D inverseTransform(Dimension2D dimSrc, Dimension2D dimDst) {
+        if (dimDst == null) {
+            dimDst = (Dimension2D) dimSrc.clone();
+        }
 
-		double scale;
+        double width = dimSrc.getWidth();
+        double height = dimSrc.getHeight();
+        double m00 = getScaleX();
+        double m11 = getScaleY();
+        double m01 = getShearX();
+        double m10 = getShearY();
+        double det = m00 * m11 - m01 * m10;
 
-		switch (getType()) {
-			case AffineTransform.TYPE_IDENTITY:
-				if (rectSrc != rectDst)
-					rectDst.setRect(rectSrc);
-				break;
+        try {
+            if (Math.abs(det) <= Double.MIN_VALUE) {
+                throw new NoninvertibleTransformException("Determinant is " + det);
+            }
+            dimDst.setSize((width * m11 - height * m01) / det, (height * m00 - width * m10) / det);
+        }
+        catch (NoninvertibleTransformException e) {
+            e.printStackTrace();
+        }
 
-			case AffineTransform.TYPE_TRANSLATION:
-				rectDst.setRect(rectSrc.getX() + getTranslateX(), 
-								rectSrc.getY() + getTranslateY(), 
-								rectSrc.getWidth(), 
-								rectSrc.getHeight());
-				break;
+        return dimDst;
+    }
 
-			case AffineTransform.TYPE_UNIFORM_SCALE:
-				scale = getScaleX();
-				rectDst.setRect(rectSrc.getX() * scale, 
-								rectSrc.getY() * scale, 
-								rectSrc.getWidth() * scale, 
-								rectSrc.getHeight() * scale);
-				break;
-				
-			case AffineTransform.TYPE_TRANSLATION | AffineTransform.TYPE_UNIFORM_SCALE:
-				scale = getScaleX();
-				rectDst.setRect((rectSrc.getX() * scale) + getTranslateX(), 
-								(rectSrc.getY() * scale) + getTranslateY(), 
-								rectSrc.getWidth() * scale, 
-								rectSrc.getHeight() * scale);
-				break;
+    public Rectangle2D transform(Rectangle2D rectSrc, Rectangle2D rectDst) {
+        if (rectDst == null) {
+            rectDst = (Rectangle2D) rectSrc.clone();
+        }
 
-			default :
-				double[] pts = rectToArray(rectSrc);
-				transform(pts, 0, pts, 0, 4);
-				rectFromArray(rectDst, pts);				
-				break;
-		}
-		
-		
-		return rectDst;
-	}
-			
-	public Rectangle2D inverseTransform(Rectangle2D rectSrc, Rectangle2D rectDst) {
-		if (rectDst == null) {
-			rectDst = (Rectangle2D) rectSrc.clone();
-		}
-		
-		if (rectSrc.isEmpty()) {
-			rectDst.setRect(rectSrc);
-			if (rectDst instanceof PBounds) {
-				((PBounds)rectDst).reset();
-			}
-			return rectDst; 		
-		}
-		
-		double scale;
+        if (rectSrc.isEmpty()) {
+            rectDst.setRect(rectSrc);
+            if (rectDst instanceof PBounds) {
+                ((PBounds) rectDst).reset();
+            }
+            return rectDst;
+        }
 
-		switch (getType()) {
-			case AffineTransform.TYPE_IDENTITY:
-				if (rectSrc != rectDst)
-					rectDst.setRect(rectSrc);
-				break;
+        double scale;
 
-			case AffineTransform.TYPE_TRANSLATION:
-				rectDst.setRect(rectSrc.getX() - getTranslateX(), 
-								rectSrc.getY() - getTranslateY(), 
-								rectSrc.getWidth(), 
-								rectSrc.getHeight());
-				break;
+        switch (getType()) {
+            case AffineTransform.TYPE_IDENTITY:
+                if (rectSrc != rectDst)
+                    rectDst.setRect(rectSrc);
+                break;
 
-			case AffineTransform.TYPE_UNIFORM_SCALE:
-				scale = 1 / getScaleX();
-				rectDst.setRect(rectSrc.getX() * scale, 
-								rectSrc.getY() * scale, 
-								rectSrc.getWidth() * scale, 
-								rectSrc.getHeight() * scale);
-				break;
-				
-			case AffineTransform.TYPE_TRANSLATION | AffineTransform.TYPE_UNIFORM_SCALE:
-				scale = 1 / getScaleX();
-				rectDst.setRect((rectSrc.getX() - getTranslateX()) * scale, 
-								(rectSrc.getY() - getTranslateY()) * scale, 
-								rectSrc.getWidth() * scale, 
-								rectSrc.getHeight() * scale);
-				break;
-				
-			default :
-				double[] pts = rectToArray(rectSrc);
-				try {
-					inverseTransform(pts, 0, pts, 0, 4);
-				} catch (NoninvertibleTransformException e) {
-					e.printStackTrace();
-				}		
-				rectFromArray(rectDst, pts);
-				break;
-		}
-			
-		return rectDst;
-	}
-	
-	private static double[] rectToArray(Rectangle2D aRectangle) {
-		PTS1[0] = aRectangle.getX();
-		PTS1[1] = aRectangle.getY();
-		PTS1[2] = PTS1[0] + aRectangle.getWidth();
-		PTS1[3] = PTS1[1];
-		PTS1[4] = PTS1[0] + aRectangle.getWidth();
-		PTS1[5] = PTS1[1] + aRectangle.getHeight();
-		PTS1[6] = PTS1[0];
-		PTS1[7] = PTS1[1] + aRectangle.getHeight();
-		return PTS1;
-	}
+            case AffineTransform.TYPE_TRANSLATION:
+                rectDst.setRect(rectSrc.getX() + getTranslateX(), rectSrc.getY() + getTranslateY(), rectSrc.getWidth(),
+                        rectSrc.getHeight());
+                break;
 
-	private static void rectFromArray(Rectangle2D aRectangle, double[] pts) {
-		double minX = pts[0];
-		double minY = pts[1];
-		double maxX = pts[0];
-		double maxY = pts[1];
+            case AffineTransform.TYPE_UNIFORM_SCALE:
+                scale = getScaleX();
+                rectDst.setRect(rectSrc.getX() * scale, rectSrc.getY() * scale, rectSrc.getWidth() * scale, rectSrc
+                        .getHeight()
+                        * scale);
+                break;
 
-		double x;
-		double y;
+            case AffineTransform.TYPE_TRANSLATION | AffineTransform.TYPE_UNIFORM_SCALE:
+                scale = getScaleX();
+                rectDst.setRect((rectSrc.getX() * scale) + getTranslateX(), (rectSrc.getY() * scale) + getTranslateY(),
+                        rectSrc.getWidth() * scale, rectSrc.getHeight() * scale);
+                break;
 
-		for (int i = 1; i < 4; i++) {
-			x = pts[2 * i];
-			y = pts[(2 * i) + 1];
-			
-			if (x < minX) {
-				minX = x;
-			}
-			if (y < minY) {
-				minY = y;
-			}
-			if (x > maxX) {
-				maxX = x;
-			}
-			if (y > maxY) {
-				maxY = y;
-			}
-		}
-		aRectangle.setRect(minX, minY, maxX - minX, maxY - minY);
-	}	
+            default:
+                double[] pts = rectToArray(rectSrc);
+                transform(pts, 0, pts, 0, 4);
+                rectFromArray(rectDst, pts);
+                break;
+        }
+
+        return rectDst;
+    }
+
+    public Rectangle2D inverseTransform(Rectangle2D rectSrc, Rectangle2D rectDst) {
+        if (rectDst == null) {
+            rectDst = (Rectangle2D) rectSrc.clone();
+        }
+
+        if (rectSrc.isEmpty()) {
+            rectDst.setRect(rectSrc);
+            if (rectDst instanceof PBounds) {
+                ((PBounds) rectDst).reset();
+            }
+            return rectDst;
+        }
+
+        double scale;
+
+        switch (getType()) {
+            case AffineTransform.TYPE_IDENTITY:
+                if (rectSrc != rectDst)
+                    rectDst.setRect(rectSrc);
+                break;
+
+            case AffineTransform.TYPE_TRANSLATION:
+                rectDst.setRect(rectSrc.getX() - getTranslateX(), rectSrc.getY() - getTranslateY(), rectSrc.getWidth(),
+                        rectSrc.getHeight());
+                break;
+
+            case AffineTransform.TYPE_UNIFORM_SCALE:
+                scale = 1 / getScaleX();
+                rectDst.setRect(rectSrc.getX() * scale, rectSrc.getY() * scale, rectSrc.getWidth() * scale, rectSrc
+                        .getHeight()
+                        * scale);
+                break;
+
+            case AffineTransform.TYPE_TRANSLATION | AffineTransform.TYPE_UNIFORM_SCALE:
+                scale = 1 / getScaleX();
+                rectDst.setRect((rectSrc.getX() - getTranslateX()) * scale, (rectSrc.getY() - getTranslateY()) * scale,
+                        rectSrc.getWidth() * scale, rectSrc.getHeight() * scale);
+                break;
+
+            default:
+                double[] pts = rectToArray(rectSrc);
+                try {
+                    inverseTransform(pts, 0, pts, 0, 4);
+                }
+                catch (NoninvertibleTransformException e) {
+                    e.printStackTrace();
+                }
+                rectFromArray(rectDst, pts);
+                break;
+        }
+
+        return rectDst;
+    }
+
+    private static double[] rectToArray(Rectangle2D aRectangle) {
+        PTS1[0] = aRectangle.getX();
+        PTS1[1] = aRectangle.getY();
+        PTS1[2] = PTS1[0] + aRectangle.getWidth();
+        PTS1[3] = PTS1[1];
+        PTS1[4] = PTS1[0] + aRectangle.getWidth();
+        PTS1[5] = PTS1[1] + aRectangle.getHeight();
+        PTS1[6] = PTS1[0];
+        PTS1[7] = PTS1[1] + aRectangle.getHeight();
+        return PTS1;
+    }
+
+    private static void rectFromArray(Rectangle2D aRectangle, double[] pts) {
+        double minX = pts[0];
+        double minY = pts[1];
+        double maxX = pts[0];
+        double maxY = pts[1];
+
+        double x;
+        double y;
+
+        for (int i = 1; i < 4; i++) {
+            x = pts[2 * i];
+            y = pts[(2 * i) + 1];
+
+            if (x < minX) {
+                minX = x;
+            }
+            if (y < minY) {
+                minY = y;
+            }
+            if (x > maxX) {
+                maxX = x;
+            }
+            if (y > maxY) {
+                maxY = y;
+            }
+        }
+        aRectangle.setRect(minX, minY, maxX - minX, maxY - minY);
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/util/PBounds.java b/core/src/main/java/edu/umd/cs/piccolo/util/PBounds.java
index 7c2de04..be1fd51 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/util/PBounds.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/util/PBounds.java
@@ -39,258 +39,263 @@
 
 /**
  * PBounds is simply a Rectangle2D.Double with extra methods that more
- * properly deal with the case when the rectangle is "empty".  A PBounds
- * has an extra bit to store emptiness.  In this state, adding new geometry
- * replaces the current geometry. A PBounds is emptied with the reset() method.
- * A useful side effect of the reset method is that it only modifies the fIsEmpty
- * variable, the other x, y, with, height variables are left alone. This is used
- * by Piccolo's layout management system to see if a the full bounds of a node
- * has really changed when it is recomputed. See PNode.validateLayout.
+ * properly deal with the case when the rectangle is "empty". A PBounds has an
+ * extra bit to store emptiness. In this state, adding new geometry replaces the
+ * current geometry. A PBounds is emptied with the reset() method. A useful side
+ * effect of the reset method is that it only modifies the fIsEmpty variable,
+ * the other x, y, with, height variables are left alone. This is used by
+ * Piccolo's layout management system to see if a the full bounds of a node has
+ * really changed when it is recomputed. See PNode.validateLayout.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PBounds extends Rectangle2D.Double implements Serializable {
 
-	private boolean isEmpty = true;
+    private boolean isEmpty = true;
 
-	public PBounds() {
-		super();
-	}
+    public PBounds() {
+        super();
+    }
 
-	public PBounds(PBounds aBounds) {
-		this(aBounds.x, aBounds.y, aBounds.width, aBounds.height);
-		isEmpty = aBounds.isEmpty();
-	}
+    public PBounds(PBounds aBounds) {
+        this(aBounds.x, aBounds.y, aBounds.width, aBounds.height);
+        isEmpty = aBounds.isEmpty();
+    }
 
-	public PBounds(Rectangle2D aBounds) {
-		this(aBounds.getX(), aBounds.getY(), aBounds.getWidth(), aBounds.getHeight());
-		isEmpty = aBounds.isEmpty();
-	}
+    public PBounds(Rectangle2D aBounds) {
+        this(aBounds.getX(), aBounds.getY(), aBounds.getWidth(), aBounds.getHeight());
+        isEmpty = aBounds.isEmpty();
+    }
 
-	public PBounds(Point2D aCenterPoint, double insetX, double insetY) {
-		this(aCenterPoint.getX(), aCenterPoint.getY(), 0, 0);
-		inset(insetX, insetY);
-	}
-	
-	public PBounds(double x, double y, double width, double height) {
-		super(x, y, width, height);
-		isEmpty = false;
-	}
+    public PBounds(Point2D aCenterPoint, double insetX, double insetY) {
+        this(aCenterPoint.getX(), aCenterPoint.getY(), 0, 0);
+        inset(insetX, insetY);
+    }
 
-	public Object clone() {
-		return new PBounds(this);
-	}
+    public PBounds(double x, double y, double width, double height) {
+        super(x, y, width, height);
+        isEmpty = false;
+    }
 
-	public boolean isEmpty() {
-		return isEmpty;
-	}
+    public Object clone() {
+        return new PBounds(this);
+    }
 
-	public PBounds reset() {
-		isEmpty = true;
-		return this;
-	}
+    public boolean isEmpty() {
+        return isEmpty;
+    }
 
-	public PBounds resetToZero() {
-		x = 0;
-		y = 0;
-		width = 0;
-		height = 0;
-		isEmpty = true;
-		return this;
-	}
+    public PBounds reset() {
+        isEmpty = true;
+        return this;
+    }
 
-	public void setRect(Rectangle2D r) {
-		super.setRect(r);
-		isEmpty = false;
-	}
+    public PBounds resetToZero() {
+        x = 0;
+        y = 0;
+        width = 0;
+        height = 0;
+        isEmpty = true;
+        return this;
+    }
 
-	public void setRect(PBounds b) {
-		isEmpty = b.isEmpty;
-		x = b.x;
-		y = b.y;
-		width = b.width;
-		height = b.height;
-	}
+    public void setRect(Rectangle2D r) {
+        super.setRect(r);
+        isEmpty = false;
+    }
 
-	public void setRect(double x, double y, double w, double h) {
-		this.x = x;
-		this.y = y;
-		this.width = w;
-		this.height = h;
-		isEmpty = false;
-	}
+    public void setRect(PBounds b) {
+        isEmpty = b.isEmpty;
+        x = b.x;
+        y = b.y;
+        width = b.width;
+        height = b.height;
+    }
 
-	public void add(double newx, double newy) {
-		if (isEmpty) {
-			setRect(newx, newy, 0, 0);
-			isEmpty = false;
-		} else {
-			super.add(newx, newy);
-		}
-	}
+    public void setRect(double x, double y, double w, double h) {
+        this.x = x;
+        this.y = y;
+        this.width = w;
+        this.height = h;
+        isEmpty = false;
+    }
 
-	public void add(Rectangle2D r) {
-		if (isEmpty) {
-			setRect(r);
-		} else {
-			super.add(r);
-		}
-	}
+    public void add(double newx, double newy) {
+        if (isEmpty) {
+            setRect(newx, newy, 0, 0);
+            isEmpty = false;
+        }
+        else {
+            super.add(newx, newy);
+        }
+    }
 
-	// optimized add when adding two PBounds together.
-	public void add(PBounds r) {
-		if (r.isEmpty) {
-			return;
-		} else if (isEmpty) {
-			x = r.x;
-			y = r.y;
-			width = r.width;
-			height = r.height;
-			isEmpty = false;
-		} else {
-			double x1 = (x <= r.x) ? x : r.x;
-			double y1 = (y <= r.y) ? y : r.y;
-			double x2 = ((x + width) >= (r.x + r.width)) ? (x + width) : (r.x + r.width);			
-			double y2 = ((y + height) >= (r.y + r.height)) ? (y + height) : (r.y + r.height);
+    public void add(Rectangle2D r) {
+        if (isEmpty) {
+            setRect(r);
+        }
+        else {
+            super.add(r);
+        }
+    }
 
-			x = x1;
-			y = y1;
-			width = x2 - x1;
-			height = y2 - y1;
-			isEmpty = false;
-		}
-	}
-	
-	public Point2D getOrigin() {
-		return new Point2D.Double(x, y);
-	}
+    // optimized add when adding two PBounds together.
+    public void add(PBounds r) {
+        if (r.isEmpty) {
+            return;
+        }
+        else if (isEmpty) {
+            x = r.x;
+            y = r.y;
+            width = r.width;
+            height = r.height;
+            isEmpty = false;
+        }
+        else {
+            double x1 = (x <= r.x) ? x : r.x;
+            double y1 = (y <= r.y) ? y : r.y;
+            double x2 = ((x + width) >= (r.x + r.width)) ? (x + width) : (r.x + r.width);
+            double y2 = ((y + height) >= (r.y + r.height)) ? (y + height) : (r.y + r.height);
 
-	public PBounds setOrigin(double x, double y) {
-		this.x = x;
-		this.y = y;
-		isEmpty = false;
-		return this;
-	}
+            x = x1;
+            y = y1;
+            width = x2 - x1;
+            height = y2 - y1;
+            isEmpty = false;
+        }
+    }
 
-	public Dimension2D getSize() {
-		return new PDimension(width, height);
-	}
-	
-	public void setSize(double width, double height) {
-		setRect(x, y, width, height);
-	}
+    public Point2D getOrigin() {
+        return new Point2D.Double(x, y);
+    }
 
-	public Point2D getCenter2D() {
-		return new Point2D.Double(getCenterX(), getCenterY());
-	}
+    public PBounds setOrigin(double x, double y) {
+        this.x = x;
+        this.y = y;
+        isEmpty = false;
+        return this;
+    }
 
-	public PBounds moveBy(double dx, double dy) {
-		setOrigin(x + dx, y + dy);
-		return this;
-	}
+    public Dimension2D getSize() {
+        return new PDimension(width, height);
+    }
 
-	public void expandNearestIntegerDimensions() {
-		x = Math.floor(x);
-		y = Math.floor(y);
-		width = Math.ceil(width);
-		height = Math.ceil(height);
-	}
-	
-	public PBounds inset(double dx, double dy) {
-		setRect(x + dx,
-				y + dy,
-				width - (dx*2),
-				height - (dy*2));
-		return this;
-	}
+    public void setSize(double width, double height) {
+        setRect(x, y, width, height);
+    }
 
-	public PDimension deltaRequiredToCenter(Rectangle2D b) {
-		PDimension result = new PDimension();
-		double xDelta = getCenterX() - b.getCenterX();
-		double yDelta = getCenterY() - b.getCenterY();
-		result.setSize(xDelta, yDelta);		
-		return result;
-	}
-	
-	public PDimension deltaRequiredToContain(Rectangle2D b) {
-		PDimension result = new PDimension();
-		
-		if (!contains(b)) {
-			double bMaxX = b.getMaxX();
-			double bMinX = b.getMinX();
-			double bMaxY = b.getMaxY();
-			double bMinY = b.getMinY();
-			double maxX = getMaxX();
-			double minX = getMinX();
-			double maxY = getMaxY();
-			double minY = getMinY();
+    public Point2D getCenter2D() {
+        return new Point2D.Double(getCenterX(), getCenterY());
+    }
 
-			if (!(bMaxX > maxX && bMinX < minX)) {
-				if (bMaxX > maxX || bMinX < minX) {
-					double difMaxX = bMaxX - maxX;
-					double difMinX = bMinX - minX;
-					if (Math.abs(difMaxX) < Math.abs(difMinX)) {
-						result.width = difMaxX;
-					} else {
-						result.width = difMinX;
-					}
-				}				
-			}
+    public PBounds moveBy(double dx, double dy) {
+        setOrigin(x + dx, y + dy);
+        return this;
+    }
 
-			if (!(bMaxY > maxY && bMinY < minY)) {
-				if (bMaxY > maxY || bMinY < minY) {
-					double difMaxY = bMaxY - maxY;
-					double difMinY = bMinY - minY;
-					if (Math.abs(difMaxY) < Math.abs(difMinY)) {
-						result.height = difMaxY;
-					} else {
-						result.height = difMinY;
-					}
-				}
-			}
-		}
-		
-		return result;
-	}
+    public void expandNearestIntegerDimensions() {
+        x = Math.floor(x);
+        y = Math.floor(y);
+        width = Math.ceil(width);
+        height = Math.ceil(height);
+    }
 
-	private void writeObject(ObjectOutputStream out) throws IOException {
-		out.defaultWriteObject();
-		out.writeDouble(x);
-		out.writeDouble(y);
-		out.writeDouble(width);
-		out.writeDouble(height);
-	}
+    public PBounds inset(double dx, double dy) {
+        setRect(x + dx, y + dy, width - (dx * 2), height - (dy * 2));
+        return this;
+    }
 
-	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
-		in.defaultReadObject();
-		x = in.readDouble();
-		y = in.readDouble();
-		width = in.readDouble();
-		height = in.readDouble();
-	}
-	
-	public String toString() {
-		StringBuffer result = new StringBuffer();
+    public PDimension deltaRequiredToCenter(Rectangle2D b) {
+        PDimension result = new PDimension();
+        double xDelta = getCenterX() - b.getCenterX();
+        double yDelta = getCenterY() - b.getCenterY();
+        result.setSize(xDelta, yDelta);
+        return result;
+    }
 
-		result.append(getClass().getName().replaceAll(".*\\.", ""));
-		result.append('[');
-		
-		if (isEmpty) {
-			result.append("EMPTY");
-		} else {
-			result.append("x=");
-			result.append(x);
-			result.append(",y=");
-			result.append(y);
-			result.append(",width=");
-			result.append(width);
-			result.append(",height=");
-			result.append(height);
-		}
-		
-		result.append(']');
-		
-		return result.toString();
-	}	
+    public PDimension deltaRequiredToContain(Rectangle2D b) {
+        PDimension result = new PDimension();
+
+        if (!contains(b)) {
+            double bMaxX = b.getMaxX();
+            double bMinX = b.getMinX();
+            double bMaxY = b.getMaxY();
+            double bMinY = b.getMinY();
+            double maxX = getMaxX();
+            double minX = getMinX();
+            double maxY = getMaxY();
+            double minY = getMinY();
+
+            if (!(bMaxX > maxX && bMinX < minX)) {
+                if (bMaxX > maxX || bMinX < minX) {
+                    double difMaxX = bMaxX - maxX;
+                    double difMinX = bMinX - minX;
+                    if (Math.abs(difMaxX) < Math.abs(difMinX)) {
+                        result.width = difMaxX;
+                    }
+                    else {
+                        result.width = difMinX;
+                    }
+                }
+            }
+
+            if (!(bMaxY > maxY && bMinY < minY)) {
+                if (bMaxY > maxY || bMinY < minY) {
+                    double difMaxY = bMaxY - maxY;
+                    double difMinY = bMinY - minY;
+                    if (Math.abs(difMaxY) < Math.abs(difMinY)) {
+                        result.height = difMaxY;
+                    }
+                    else {
+                        result.height = difMinY;
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+        out.writeDouble(x);
+        out.writeDouble(y);
+        out.writeDouble(width);
+        out.writeDouble(height);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        x = in.readDouble();
+        y = in.readDouble();
+        width = in.readDouble();
+        height = in.readDouble();
+    }
+
+    public String toString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append(getClass().getName().replaceAll(".*\\.", ""));
+        result.append('[');
+
+        if (isEmpty) {
+            result.append("EMPTY");
+        }
+        else {
+            result.append("x=");
+            result.append(x);
+            result.append(",y=");
+            result.append(y);
+            result.append(",width=");
+            result.append(width);
+            result.append(",height=");
+            result.append(height);
+        }
+
+        result.append(']');
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/util/PDebug.java b/core/src/main/java/edu/umd/cs/piccolo/util/PDebug.java
index c049fc3..c758ee1 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/util/PDebug.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/util/PDebug.java
@@ -38,163 +38,165 @@
 /**
  * PDebug is used to set framework wide debugging flags.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PDebug {
-	
-	public static boolean debugRegionManagement = false;	
-	public static boolean debugPaintCalls = false;	
-	public static boolean debugPrintFrameRate = false;	
-	public static boolean debugPrintUsedMemory = false; 
-	public static boolean debugBounds = false;
-	public static boolean debugFullBounds = false;
-	public static boolean debugThreads = false;
-	public static int printResultsFrameRate = 10;
-	
-	private static int debugPaintColor;
 
-	private static long framesProcessed;
-	private static long startProcessingOutputTime;
-	private static long startProcessingInputTime;
-	private static long processOutputTime;
-	private static long processInputTime;
-	private static boolean processingOutput;
+    public static boolean debugRegionManagement = false;
+    public static boolean debugPaintCalls = false;
+    public static boolean debugPrintFrameRate = false;
+    public static boolean debugPrintUsedMemory = false;
+    public static boolean debugBounds = false;
+    public static boolean debugFullBounds = false;
+    public static boolean debugThreads = false;
+    public static int printResultsFrameRate = 10;
 
-	private PDebug() {
-		super();
-	}
-	
-	public static Color getDebugPaintColor() {
-		int color = 100 + (debugPaintColor++ % 10) * 10;
-		return new Color(color, color, color, 150);
-	}
-	
-	// called when scene graph needs update.
-	public static void scheduleProcessInputs() {
-		if (debugThreads && !SwingUtilities.isEventDispatchThread()) {
-			System.out.println("scene graph manipulated on wrong thread");
-		}
-	}
-	
-	public static void processRepaint() {
-		if (processingOutput && debugPaintCalls) {
-			System.err.println("Got repaint while painting scene. This can result in a recursive process that degrades performance.");
-		}
-		
-		if (debugThreads && !SwingUtilities.isEventDispatchThread()) {
-			System.out.println("repaint called on wrong thread");
-		}
-	}
-	
-	public static boolean getProcessingOutput() {
-		return processingOutput;
-	}
-	
-	public static void startProcessingOutput() {
-		processingOutput = true;
-		startProcessingOutputTime = System.currentTimeMillis();
-	}
-	
-	public static void endProcessingOutput(Graphics g) {
-		processOutputTime += (System.currentTimeMillis() - startProcessingOutputTime);
-		framesProcessed++;
-				
-		if (PDebug.debugPrintFrameRate) {
-			if (framesProcessed % printResultsFrameRate == 0) {
-				System.out.println("Process output frame rate: " + getOutputFPS() + " fps");
-				System.out.println("Process input frame rate: " + getInputFPS() + " fps");
-				System.out.println("Total frame rate: " + getTotalFPS() + " fps");
-				System.out.println();				
-				resetFPSTiming();				
-			}
-		}
-		
-		if (PDebug.debugPrintUsedMemory) {
-			if (framesProcessed % printResultsFrameRate == 0) { 		
-				System.out.println("Approximate used memory: " + getApproximateUsedMemory() / 1024 + " k");
-			}
-		}
-		
-		if (PDebug.debugRegionManagement) {
-			Graphics2D g2 = (Graphics2D)g;
-			g.setColor(PDebug.getDebugPaintColor());
-			g2.fill(g.getClipBounds().getBounds2D());
-		}
-		
-		processingOutput = false;
-	}
+    private static int debugPaintColor;
 
-	public static void startProcessingInput() {
-		startProcessingInputTime = System.currentTimeMillis();
-	}
-	
-	public static void endProcessingInput() {
-		processInputTime += (System.currentTimeMillis() - startProcessingInputTime);
-	}
-	
-	/**
-	 * Return how many frames are processed and painted per second. 
-	 * Note that since piccolo doesn't paint continuously this rate
-	 * will be slow unless you are interacting with the system or have
-	 * activities scheduled.
-	 */
-	public static double getTotalFPS() {
-		if ((framesProcessed > 0)) {
-			return 1000.0 / ((processInputTime + processOutputTime) / (double) framesProcessed);
-		} else {
-			return 0;
-		}
-	}
+    private static long framesProcessed;
+    private static long startProcessingOutputTime;
+    private static long startProcessingInputTime;
+    private static long processOutputTime;
+    private static long processInputTime;
+    private static boolean processingOutput;
 
-	/**
-	 * Return the frames per second used to process 
-	 * input events and activities.
-	 */
-	public static double getInputFPS() {
-		if ((processInputTime > 0) && (framesProcessed > 0)) {
-			return 1000.0 / (processInputTime / (double) framesProcessed);
-		} else {
-			return 0;
-		}
-	}
-	
-	/**
-	 * Return the frames per seconds used to paint
-	 * graphics to the screen.
-	 */
-	public static double getOutputFPS() {
-		if ((processOutputTime > 0) && (framesProcessed > 0)) {
-			return 1000.0 / (processOutputTime / (double) framesProcessed);
-		} else {
-			return 0;
-		}
-	}
-	
-	/**
-	 * Return the number of frames that have been processed since the last
-	 * time resetFPSTiming was called.
-	 */
-	public long getFramesProcessed() {
-		return framesProcessed;
-	}
-	
-	/**
-	 * Reset the variables used to track FPS. If you reset seldom they you will
-	 * get good average FPS values, if you reset more often only the frames recorded
-	 * after the last reset will be taken into consideration.
-	 */
-	public static void resetFPSTiming() {
-		framesProcessed = 0;
-		processInputTime = 0;
-		processOutputTime = 0;
-	}
-	
-	public static long getApproximateUsedMemory() {
-		System.gc();
-		System.runFinalization();
-		long totalMemory = Runtime.getRuntime().totalMemory();
-		long free = Runtime.getRuntime().freeMemory();
-		return totalMemory - free;
-	}	
+    private PDebug() {
+        super();
+    }
+
+    public static Color getDebugPaintColor() {
+        int color = 100 + (debugPaintColor++ % 10) * 10;
+        return new Color(color, color, color, 150);
+    }
+
+    // called when scene graph needs update.
+    public static void scheduleProcessInputs() {
+        if (debugThreads && !SwingUtilities.isEventDispatchThread()) {
+            System.out.println("scene graph manipulated on wrong thread");
+        }
+    }
+
+    public static void processRepaint() {
+        if (processingOutput && debugPaintCalls) {
+            System.err
+                    .println("Got repaint while painting scene. This can result in a recursive process that degrades performance.");
+        }
+
+        if (debugThreads && !SwingUtilities.isEventDispatchThread()) {
+            System.out.println("repaint called on wrong thread");
+        }
+    }
+
+    public static boolean getProcessingOutput() {
+        return processingOutput;
+    }
+
+    public static void startProcessingOutput() {
+        processingOutput = true;
+        startProcessingOutputTime = System.currentTimeMillis();
+    }
+
+    public static void endProcessingOutput(Graphics g) {
+        processOutputTime += (System.currentTimeMillis() - startProcessingOutputTime);
+        framesProcessed++;
+
+        if (PDebug.debugPrintFrameRate) {
+            if (framesProcessed % printResultsFrameRate == 0) {
+                System.out.println("Process output frame rate: " + getOutputFPS() + " fps");
+                System.out.println("Process input frame rate: " + getInputFPS() + " fps");
+                System.out.println("Total frame rate: " + getTotalFPS() + " fps");
+                System.out.println();
+                resetFPSTiming();
+            }
+        }
+
+        if (PDebug.debugPrintUsedMemory) {
+            if (framesProcessed % printResultsFrameRate == 0) {
+                System.out.println("Approximate used memory: " + getApproximateUsedMemory() / 1024 + " k");
+            }
+        }
+
+        if (PDebug.debugRegionManagement) {
+            Graphics2D g2 = (Graphics2D) g;
+            g.setColor(PDebug.getDebugPaintColor());
+            g2.fill(g.getClipBounds().getBounds2D());
+        }
+
+        processingOutput = false;
+    }
+
+    public static void startProcessingInput() {
+        startProcessingInputTime = System.currentTimeMillis();
+    }
+
+    public static void endProcessingInput() {
+        processInputTime += (System.currentTimeMillis() - startProcessingInputTime);
+    }
+
+    /**
+     * Return how many frames are processed and painted per second. Note that
+     * since piccolo doesn't paint continuously this rate will be slow unless
+     * you are interacting with the system or have activities scheduled.
+     */
+    public static double getTotalFPS() {
+        if ((framesProcessed > 0)) {
+            return 1000.0 / ((processInputTime + processOutputTime) / (double) framesProcessed);
+        }
+        else {
+            return 0;
+        }
+    }
+
+    /**
+     * Return the frames per second used to process input events and activities.
+     */
+    public static double getInputFPS() {
+        if ((processInputTime > 0) && (framesProcessed > 0)) {
+            return 1000.0 / (processInputTime / (double) framesProcessed);
+        }
+        else {
+            return 0;
+        }
+    }
+
+    /**
+     * Return the frames per seconds used to paint graphics to the screen.
+     */
+    public static double getOutputFPS() {
+        if ((processOutputTime > 0) && (framesProcessed > 0)) {
+            return 1000.0 / (processOutputTime / (double) framesProcessed);
+        }
+        else {
+            return 0;
+        }
+    }
+
+    /**
+     * Return the number of frames that have been processed since the last time
+     * resetFPSTiming was called.
+     */
+    public long getFramesProcessed() {
+        return framesProcessed;
+    }
+
+    /**
+     * Reset the variables used to track FPS. If you reset seldom they you will
+     * get good average FPS values, if you reset more often only the frames
+     * recorded after the last reset will be taken into consideration.
+     */
+    public static void resetFPSTiming() {
+        framesProcessed = 0;
+        processInputTime = 0;
+        processOutputTime = 0;
+    }
+
+    public static long getApproximateUsedMemory() {
+        System.gc();
+        System.runFinalization();
+        long totalMemory = Runtime.getRuntime().totalMemory();
+        long free = Runtime.getRuntime().freeMemory();
+        return totalMemory - free;
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/util/PDimension.java b/core/src/main/java/edu/umd/cs/piccolo/util/PDimension.java
index 3131ef7..272440b 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/util/PDimension.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/util/PDimension.java
@@ -34,60 +34,61 @@
 import java.io.Serializable;
 
 /**
- * PDimension this class should be removed once a concrete Dimension2D 
- * that supports doubles is added to java. 
+ * PDimension this class should be removed once a concrete Dimension2D
+ * that supports doubles is added to java.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PDimension extends Dimension2D implements Serializable {
 
-	public double width;
-	public double height;
+    public double width;
+    public double height;
 
-	public PDimension() {
-		super();
-	}
+    public PDimension() {
+        super();
+    }
 
-	public PDimension(Dimension2D aDimension) {
-		this(aDimension.getWidth(), aDimension.getHeight());
-	}
-	
-	public PDimension(double aWidth, double aHeight) {
-		super();
-		width = aWidth;
-		height = aHeight;
-	}
+    public PDimension(Dimension2D aDimension) {
+        this(aDimension.getWidth(), aDimension.getHeight());
+    }
 
-	public PDimension(Point2D p1, Point2D p2) {
-		width = p2.getX() - p1.getX();
-		height = p2.getY() - p1.getY();
-	}
+    public PDimension(double aWidth, double aHeight) {
+        super();
+        width = aWidth;
+        height = aHeight;
+    }
 
-	public double getHeight() {
-		return height;
-	}
+    public PDimension(Point2D p1, Point2D p2) {
+        width = p2.getX() - p1.getX();
+        height = p2.getY() - p1.getY();
+    }
 
-	public double getWidth() {
-		return width;
-	}
+    public double getHeight() {
+        return height;
+    }
 
-	public void setSize(double aWidth, double aHeight) {
-		width = aWidth;
-		height = aHeight;
-	}
-	
-	public String toString() {
-		StringBuffer result = new StringBuffer();
+    public double getWidth() {
+        return width;
+    }
 
-		result.append(super.toString().replaceAll(".*\\.", ""));
-		result.append('[');
-		result.append("width=");
-		result.append(width);
-		result.append(",height=");
-		result.append(height);
-		result.append(']');
+    public void setSize(double aWidth, double aHeight) {
+        width = aWidth;
+        height = aHeight;
+    }
 
-		return result.toString();
-	}	
+    public String toString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append(super.toString().replaceAll(".*\\.", ""));
+        result.append('[');
+        result.append("width=");
+        result.append(width);
+        result.append(",height=");
+        result.append(height);
+        result.append(']');
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/util/PNodeFilter.java b/core/src/main/java/edu/umd/cs/piccolo/util/PNodeFilter.java
index 7871d0d..9d4cc17 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/util/PNodeFilter.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/util/PNodeFilter.java
@@ -32,23 +32,24 @@
 import edu.umd.cs.piccolo.PNode;
 
 /**
- * PNodeFilter is a interface that filters (accepts or rejects) nodes. Its
- * main use is to retrieve all the children of a node the meet some criteria
+ * PNodeFilter is a interface that filters (accepts or rejects) nodes.
+ * Its main use is to retrieve all the children of a node the meet some criteria
  * by using the method PNode.getAllNodes(collection, filter);
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public interface PNodeFilter {
 
-	/**
-	 * Return true if the filter should accept the given node.
-	 */ 
-	public boolean accept(PNode aNode); 
-	
-	/**
-	 * Return true if the filter should test the children of
-	 * the given node for acceptance.
-	 */
-	public boolean acceptChildrenOf(PNode aNode);
+    /**
+     * Return true if the filter should accept the given node.
+     */
+    public boolean accept(PNode aNode);
+
+    /**
+     * Return true if the filter should test the children of the given node for
+     * acceptance.
+     */
+    public boolean acceptChildrenOf(PNode aNode);
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/util/PObjectOutputStream.java b/core/src/main/java/edu/umd/cs/piccolo/util/PObjectOutputStream.java
index 471bf2f..8a46001 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/util/PObjectOutputStream.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/util/PObjectOutputStream.java
@@ -35,87 +35,95 @@
 import java.io.OutputStream;
 import java.util.HashMap;
 
-/** 
- * PObjectOutputStream is an extension of ObjectOutputStream to handle optional 
- * elements. This is similar to the concept of Java's "weak references", but applied to
- * object serialization rather than garbage collection. Here, PObjectOutputStream
- * provides a method,  
- * To discover strong references to objects, PObjectOutputStream uses a two-phase writing 
- * process. First, a "discovery" phase is used to find out what objects are about to 
- * be serialized. This works by effectively serializing the object graph to /dev/null, recording 
- * which objects are unconditionally written using the standard writeObject method. Then,
- * in the second "write" phase, ObjectOutputStream actually serializes the data
- * to the output stream. During this phase, calls to writeConditionalObject() will
- * only write the specified object if the object was found to be serialized during the
- * discovery stage. If the object was not recorded during the discovery stage, a an optional null 
- * (the default) is unconditionally written in place of the object. To skip writting out the
- * null use  
- * By careful implementation of readObject and writeObject methods, streams serialized using
- * PObjectOutputStream can be deserialized using the standard ObjectInputStream.
+ * By careful implementation of readObject and writeObject methods, streams
+ * serialized using PObjectOutputStream can be deserialized using the standard
+ * ObjectInputStream.
  *  
+ * 
  * @version 1.0
  * @author Jon Meyer
  * @author Jesse Grosjean
  */
 public class PObjectOutputStream extends ObjectOutputStream {
 
-	private boolean writingRoot;
-	private HashMap unconditionallyWritten;
+    private boolean writingRoot;
+    private HashMap unconditionallyWritten;
 
-	public static byte[] toByteArray(Object aRoot) throws IOException {
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		PObjectOutputStream zout = new PObjectOutputStream(out);
-		zout.writeObjectTree(aRoot);
-		return out.toByteArray();
-	}
-	
-	public PObjectOutputStream(OutputStream out) throws IOException {
-		super(out);
-		unconditionallyWritten = new HashMap();
-	}
+    public static byte[] toByteArray(Object aRoot) throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        PObjectOutputStream zout = new PObjectOutputStream(out);
+        zout.writeObjectTree(aRoot);
+        return out.toByteArray();
+    }
 
-	public void writeObjectTree(Object aRoot) throws IOException {
-		writingRoot = true;
-		recordUnconditionallyWritten(aRoot); // record pass
-		writeObject(aRoot); 				 // write pass
-		writingRoot = false;
-	}
+    public PObjectOutputStream(OutputStream out) throws IOException {
+        super(out);
+        unconditionallyWritten = new HashMap();
+    }
 
-	public void writeConditionalObject(Object object) throws IOException {
-		if (!writingRoot) {
-			throw new RuntimeException("writeConditionalObject() may only be called when a root object has been written.");
-		}
-		
-		if (unconditionallyWritten.containsKey(object)) {
-			writeObject(object);
-		} else {
-			writeObject(null);
-		}
-	}
+    public void writeObjectTree(Object aRoot) throws IOException {
+        writingRoot = true;
+        recordUnconditionallyWritten(aRoot); // record pass
+        writeObject(aRoot); // write pass
+        writingRoot = false;
+    }
 
-	public void reset() throws IOException {
-		super.reset();
-		unconditionallyWritten.clear();
-	}
+    public void writeConditionalObject(Object object) throws IOException {
+        if (!writingRoot) {
+            throw new RuntimeException(
+                    "writeConditionalObject() may only be called when a root object has been written.");
+        }
 
-	protected void recordUnconditionallyWritten(Object aRoot) throws IOException {
-		class ZMarkObjectOutputStream extends PObjectOutputStream {
-			public ZMarkObjectOutputStream() throws IOException {
-				super(PUtil.NULL_OUTPUT_STREAM);
-				enableReplaceObject(true);
-			}
-			public Object replaceObject(Object object) {
-				PObjectOutputStream.this.unconditionallyWritten.put(object, Boolean.TRUE);
-				return object;
-			}	
-			public void writeConditionalObject(Object object) throws IOException {
-			}				
-		}
-		new ZMarkObjectOutputStream().writeObject(aRoot);
-	}
+        if (unconditionallyWritten.containsKey(object)) {
+            writeObject(object);
+        }
+        else {
+            writeObject(null);
+        }
+    }
+
+    public void reset() throws IOException {
+        super.reset();
+        unconditionallyWritten.clear();
+    }
+
+    protected void recordUnconditionallyWritten(Object aRoot) throws IOException {
+        class ZMarkObjectOutputStream extends PObjectOutputStream {
+            public ZMarkObjectOutputStream() throws IOException {
+                super(PUtil.NULL_OUTPUT_STREAM);
+                enableReplaceObject(true);
+            }
+
+            public Object replaceObject(Object object) {
+                PObjectOutputStream.this.unconditionallyWritten.put(object, Boolean.TRUE);
+                return object;
+            }
+
+            public void writeConditionalObject(Object object) throws IOException {
+            }
+        }
+        new ZMarkObjectOutputStream().writeObject(aRoot);
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/util/PPaintContext.java b/core/src/main/java/edu/umd/cs/piccolo/util/PPaintContext.java
index a548aa2..ce73085 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/util/PPaintContext.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/util/PPaintContext.java
@@ -42,182 +42,185 @@
 import edu.umd.cs.piccolo.PCamera;
 
 /**
-* PPaintContext is used by piccolo nodes to paint themselves on the screen.
- * PPaintContext wraps a Graphics2D to implement painting.
+ * PPaintContext is used by piccolo nodes to paint themselves on the
+ * screen. PPaintContext wraps a Graphics2D to implement painting.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PPaintContext {
 
-	public static final int LOW_QUALITY_RENDERING = 0;
-	public static final int HIGH_QUALITY_RENDERING = 1;
-	
-	public static FontRenderContext RENDER_QUALITY_LOW_FRC = new FontRenderContext(null, false, true);
-	public static FontRenderContext RENDER_QUALITY_HIGH_FRC = new FontRenderContext(null, true, true);	
-	public static PPaintContext CURRENT_PAINT_CONTEXT;
-	
-	private static double[] PTS = new double[4];
-		
-	private Graphics2D graphics;
-	protected PStack compositeStack;
-	protected PStack clipStack;
-	protected PStack localClipStack;
-	protected PStack cameraStack;
-	protected PStack transformStack;
-	protected int renderQuality;
-		
-	public PPaintContext(Graphics2D aGraphics) {
-		super();
-		graphics = aGraphics;
-		compositeStack = new PStack();
-		clipStack = new PStack();
-		localClipStack = new PStack();
-		cameraStack = new PStack();
-		transformStack = new PStack();
-		renderQuality = HIGH_QUALITY_RENDERING;
-		
-		Shape clip = aGraphics.getClip();
-		if (clip == null) {
-			clip = new PBounds(
-				-Integer.MAX_VALUE / 2, 
-				-Integer.MAX_VALUE / 2, 
-				Integer.MAX_VALUE,
-				Integer.MAX_VALUE);
-			aGraphics.setClip(clip);
-		}
-		
-		localClipStack.push(clip.getBounds2D());
-		
-		CURRENT_PAINT_CONTEXT = this;
-	}
-	
-	public Graphics2D getGraphics() {
-		return graphics;
-	}
+    public static final int LOW_QUALITY_RENDERING = 0;
+    public static final int HIGH_QUALITY_RENDERING = 1;
 
-	//****************************************************************
-	// Context Attributes.
-	//****************************************************************
-	
-	public Rectangle2D getLocalClip() {
-		return (Rectangle2D) localClipStack.peek();
-	}
-	
-	public double getScale() {
-		PTS[0] = 0;//x1
-		PTS[1] = 0;//y1
-		PTS[2] = 1;//x2
-		PTS[3] = 0;//y2
-		graphics.getTransform().transform(PTS, 0, PTS, 0, 2);
-		return Point2D.distance(PTS[0], PTS[1], PTS[2], PTS[3]);
-	}
-	
-	//****************************************************************
-	// Context Attribute Stacks. attributes that can be pushed and
-	// popped.
-	//****************************************************************
+    public static FontRenderContext RENDER_QUALITY_LOW_FRC = new FontRenderContext(null, false, true);
+    public static FontRenderContext RENDER_QUALITY_HIGH_FRC = new FontRenderContext(null, true, true);
+    public static PPaintContext CURRENT_PAINT_CONTEXT;
 
-	public void pushCamera(PCamera aCamera) {
-		cameraStack.push(aCamera);
-	}
-	
-	public void popCamera(PCamera aCamera) {
-		cameraStack.pop();
-	}
+    private static double[] PTS = new double[4];
 
-	public PCamera getCamera() {
-		return (PCamera) cameraStack.peek();
-	}
-	
-	public void pushClip(Shape aClip) {
-		Shape currentClip = graphics.getClip();
-		clipStack.push(currentClip);
-		graphics.clip(aClip);
-		Rectangle2D newLocalClip = aClip.getBounds2D();
-		Rectangle2D.intersect(getLocalClip(), newLocalClip, newLocalClip);		
-		localClipStack.push(newLocalClip);
-	}
+    private Graphics2D graphics;
+    protected PStack compositeStack;
+    protected PStack clipStack;
+    protected PStack localClipStack;
+    protected PStack cameraStack;
+    protected PStack transformStack;
+    protected int renderQuality;
 
-	public void popClip(Shape aClip) {
-		Shape newClip = (Shape) clipStack.pop();
-		graphics.setClip(newClip);
-		localClipStack.pop();
-	}
+    public PPaintContext(Graphics2D aGraphics) {
+        super();
+        graphics = aGraphics;
+        compositeStack = new PStack();
+        clipStack = new PStack();
+        localClipStack = new PStack();
+        cameraStack = new PStack();
+        transformStack = new PStack();
+        renderQuality = HIGH_QUALITY_RENDERING;
 
-	public void pushTransparency(float transparency) {
-		if (transparency == 1) {
-			return;
-		}
-		Composite current = graphics.getComposite();
-		float currentAlaph = 1.0f;
-		compositeStack.push(current);
+        Shape clip = aGraphics.getClip();
+        if (clip == null) {
+            clip = new PBounds(-Integer.MAX_VALUE / 2, -Integer.MAX_VALUE / 2, Integer.MAX_VALUE, Integer.MAX_VALUE);
+            aGraphics.setClip(clip);
+        }
 
-		if (current instanceof AlphaComposite) {
-			currentAlaph = ((AlphaComposite)current).getAlpha();
-		}
-		AlphaComposite newComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, currentAlaph * transparency);
-		graphics.setComposite(newComposite);
-	}
+        localClipStack.push(clip.getBounds2D());
 
-	public void popTransparency(float transparency) {
-		if (transparency == 1) {
-			return;
-		}
-		Composite c = (Composite) compositeStack.pop();
-		graphics.setComposite(c);
-	}
+        CURRENT_PAINT_CONTEXT = this;
+    }
 
-	public void pushTransform(PAffineTransform aTransform) {
-		if (aTransform == null) return;
-		Rectangle2D newLocalClip = (Rectangle2D) getLocalClip().clone();
-		aTransform.inverseTransform(newLocalClip, newLocalClip);
-		transformStack.push(graphics.getTransform());
-		localClipStack.push(newLocalClip);
-		graphics.transform(aTransform);
-	}
+    public Graphics2D getGraphics() {
+        return graphics;
+    }
 
-	public void popTransform(PAffineTransform aTransform) {
-		if (aTransform == null) return;
-		graphics.setTransform((AffineTransform)transformStack.pop());
-		localClipStack.pop();
-	}
+    // ****************************************************************
+    // Context Attributes.
+    // ****************************************************************
 
-	//****************************************************************
-	// Render Quality.
-	//****************************************************************/
-	
-	/**
-	 * Return the render quality used by this paint context.
-	 */
-	public int getRenderQuality() {
-		return renderQuality;
-	}
-	
-	/**
-	 * Set the rendering hints for this paint context. The render quality is most
-	 * often set by the rendering PCanvas. Use PCanvas.setRenderQuality() and
-	 * PCanvas.setInteractingRenderQuality() to set these values.
-	 * 
-	 * @param requestedQuality supports PPaintContext.HIGH_QUALITY_RENDERING or PPaintContext.LOW_QUALITY_RENDERING
-	 */
-	public void setRenderQuality(int requestedQuality) {
-		renderQuality = requestedQuality;
-		
-		switch (renderQuality) {
-			case HIGH_QUALITY_RENDERING:
-				graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-				graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-				graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
-				graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); 			
-				break;
+    public Rectangle2D getLocalClip() {
+        return (Rectangle2D) localClipStack.peek();
+    }
 
-			case LOW_QUALITY_RENDERING:
-				graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
-				graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
-				graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
-				graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
-				break;				
-		}
-	}	
+    public double getScale() {
+        PTS[0] = 0;// x1
+        PTS[1] = 0;// y1
+        PTS[2] = 1;// x2
+        PTS[3] = 0;// y2
+        graphics.getTransform().transform(PTS, 0, PTS, 0, 2);
+        return Point2D.distance(PTS[0], PTS[1], PTS[2], PTS[3]);
+    }
+
+    // ****************************************************************
+    // Context Attribute Stacks. attributes that can be pushed and
+    // popped.
+    // ****************************************************************
+
+    public void pushCamera(PCamera aCamera) {
+        cameraStack.push(aCamera);
+    }
+
+    public void popCamera(PCamera aCamera) {
+        cameraStack.pop();
+    }
+
+    public PCamera getCamera() {
+        return (PCamera) cameraStack.peek();
+    }
+
+    public void pushClip(Shape aClip) {
+        Shape currentClip = graphics.getClip();
+        clipStack.push(currentClip);
+        graphics.clip(aClip);
+        Rectangle2D newLocalClip = aClip.getBounds2D();
+        Rectangle2D.intersect(getLocalClip(), newLocalClip, newLocalClip);
+        localClipStack.push(newLocalClip);
+    }
+
+    public void popClip(Shape aClip) {
+        Shape newClip = (Shape) clipStack.pop();
+        graphics.setClip(newClip);
+        localClipStack.pop();
+    }
+
+    public void pushTransparency(float transparency) {
+        if (transparency == 1) {
+            return;
+        }
+        Composite current = graphics.getComposite();
+        float currentAlaph = 1.0f;
+        compositeStack.push(current);
+
+        if (current instanceof AlphaComposite) {
+            currentAlaph = ((AlphaComposite) current).getAlpha();
+        }
+        AlphaComposite newComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, currentAlaph * transparency);
+        graphics.setComposite(newComposite);
+    }
+
+    public void popTransparency(float transparency) {
+        if (transparency == 1) {
+            return;
+        }
+        Composite c = (Composite) compositeStack.pop();
+        graphics.setComposite(c);
+    }
+
+    public void pushTransform(PAffineTransform aTransform) {
+        if (aTransform == null)
+            return;
+        Rectangle2D newLocalClip = (Rectangle2D) getLocalClip().clone();
+        aTransform.inverseTransform(newLocalClip, newLocalClip);
+        transformStack.push(graphics.getTransform());
+        localClipStack.push(newLocalClip);
+        graphics.transform(aTransform);
+    }
+
+    public void popTransform(PAffineTransform aTransform) {
+        if (aTransform == null)
+            return;
+        graphics.setTransform((AffineTransform) transformStack.pop());
+        localClipStack.pop();
+    }
+
+    // ****************************************************************
+    // Render Quality.
+    // ****************************************************************/
+
+    /**
+     * Return the render quality used by this paint context.
+     */
+    public int getRenderQuality() {
+        return renderQuality;
+    }
+
+    /**
+     * Set the rendering hints for this paint context. The render quality is
+     * most often set by the rendering PCanvas. Use PCanvas.setRenderQuality()
+     * and PCanvas.setInteractingRenderQuality() to set these values.
+     * 
+     * @param requestedQuality supports PPaintContext.HIGH_QUALITY_RENDERING or
+     *            PPaintContext.LOW_QUALITY_RENDERING
+     */
+    public void setRenderQuality(int requestedQuality) {
+        renderQuality = requestedQuality;
+
+        switch (renderQuality) {
+            case HIGH_QUALITY_RENDERING:
+                graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+                graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+                graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+                graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
+                        RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+                break;
+
+            case LOW_QUALITY_RENDERING:
+                graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
+                graphics
+                        .setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
+                graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
+                graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
+                        RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+                break;
+        }
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/util/PPickPath.java b/core/src/main/java/edu/umd/cs/piccolo/util/PPickPath.java
index 0b43f97..21a56de 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/util/PPickPath.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/util/PPickPath.java
@@ -43,281 +43,286 @@
 import edu.umd.cs.piccolo.event.PInputEventListener;
 
 /**
- * PPickPath represents a ordered list of nodes that have been picked. 
- * The topmost ancestor node is the first node in the list (and should be a camera), 
- * the bottommost child node is at the end of the list. It is this bottom node that 
- * is given first chance to handle events, and that any active event handlers usually
- * manipulate.
+ * PPickPath represents a ordered list of nodes that have been picked.
+ * The topmost ancestor node is the first node in the list (and should be a
+ * camera), the bottommost child node is at the end of the list. It is this
+ * bottom node that is given first chance to handle events, and that any active
+ * event handlers usually manipulate.
  *  
- * Note that because of layers (which can be picked by multiple camera's) the ordered
- * list of nodes in a pick path do not all share a parent child relationship with the
- * nodes in the list next to them. This means that the normal localToGlobal methods don't
- * work when trying to transform geometry up and down the pick path, instead you should
- * use the pick paths canvasToLocal methods to get the mouse event points into your local
- * coord system.
+ * Note that because of layers (which can be picked by multiple camera's) the
+ * ordered list of nodes in a pick path do not all share a parent child
+ * relationship with the nodes in the list next to them. This means that the
+ * normal localToGlobal methods don't work when trying to transform geometry up
+ * and down the pick path, instead you should use the pick paths canvasToLocal
+ * methods to get the mouse event points into your local coord system.
  *  
- * Note that PInputEvent wraps most of the useful PPickPath methods, so often you
- * can use a PInputEvent directly instead of having to access its pick path.
+ * Note that PInputEvent wraps most of the useful PPickPath methods, so often
+ * you can use a PInputEvent directly instead of having to access its pick path.
  *  
+ * 
  * @see edu.umd.cs.piccolo.event.PInputEvent
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PPickPath implements PInputEventListener {
-	
-	public static PPickPath CURRENT_PICK_PATH;
 
-	private static double[] PTS = new double[4];
-	
-	private PStack nodeStack;
-	private PStack transformStack;
-	private PStack pickBoundsStack;
-	private PCamera topCamera;
-	private PCamera bottomCamera;
-	private HashMap excludedNodes;
-	
-	public PPickPath(PCamera aCamera, PBounds aScreenPickBounds) {
-		super();
-		pickBoundsStack = new PStack();
-		topCamera = aCamera;
-		nodeStack = new PStack();
-		transformStack = new PStack();
-		pickBoundsStack.push(aScreenPickBounds);
-		
-		CURRENT_PICK_PATH = this;
-	}	
-	
-	public PBounds getPickBounds() {
-		return (PBounds) pickBoundsStack.peek();
-	}
-	
-	public boolean acceptsNode(PNode node) {
-		if (excludedNodes != null) {
-			return !excludedNodes.containsKey(node);
-		}
-		return true;
-	}
-	
-	//****************************************************************
-	// Picked Nodes
-	//****************************************************************
-	
-	public void pushNode(PNode aNode) {
-		nodeStack.push(aNode);
-	}
+    public static PPickPath CURRENT_PICK_PATH;
 
-	public void popNode(PNode aNode) {
-		nodeStack.pop();
-	}
-		
-	/**
-	 * Get the bottom node on the pick path node stack. That is the last node to
-	 * be picked.
-	 */
-	public PNode getPickedNode() {
-		return (PNode) nodeStack.peek();
-	}
+    private static double[] PTS = new double[4];
 
-	//****************************************************************
-	// Iterating over picked nodes.
-	//****************************************************************
+    private PStack nodeStack;
+    private PStack transformStack;
+    private PStack pickBoundsStack;
+    private PCamera topCamera;
+    private PCamera bottomCamera;
+    private HashMap excludedNodes;
 
-	/**
-	 * Return the next node that will be picked after the current picked node.
-	 * For instance of you have two overlaping children nodes then the topmost
-	 * child will always be picked first, use this method to find the covered child.
-	 * Return the camera when no more visual will be picked.
-	 */
-	public PNode nextPickedNode() {
-		PNode picked = getPickedNode();
-		
-		if (picked == topCamera) return null;		
-		if (excludedNodes == null) excludedNodes = new HashMap();
-		
-		// exclude current picked node
-		excludedNodes.put(picked, picked);
-		
-		Object screenPickBounds = pickBoundsStack.get(0);
-		
-		// reset path state
-		pickBoundsStack = new PStack();
-		nodeStack = new PStack();
-		transformStack = new PStack();
-		pickBoundsStack = new PStack();
-		
-		pickBoundsStack.push(screenPickBounds);
+    public PPickPath(PCamera aCamera, PBounds aScreenPickBounds) {
+        super();
+        pickBoundsStack = new PStack();
+        topCamera = aCamera;
+        nodeStack = new PStack();
+        transformStack = new PStack();
+        pickBoundsStack.push(aScreenPickBounds);
 
-		// pick again
-		topCamera.fullPick(this);
-		
-		// make sure top camera is pushed.
-		if (getNodeStackReference().size() == 0) {
-			pushNode(topCamera);
-			pushTransform(topCamera.getTransformReference(false));
-		}
+        CURRENT_PICK_PATH = this;
+    }
 
-		return getPickedNode();
-	}
-	
-	/**
-	 * Get the top camera on the pick path. This is the camera that originated the
-	 * pick action.
-	 */
-	public PCamera getTopCamera() {
-		return topCamera;
-	}
+    public PBounds getPickBounds() {
+        return (PBounds) pickBoundsStack.peek();
+    }
 
-	/**
-	 * Get the bottom camera on the pick path. This may be different then the top
-	 * camera if internal cameras are in use.
-	 */ 
-	public PCamera getBottomCamera() {
-		if (bottomCamera == null) {
-			for (int i = nodeStack.size() - 1; i >= 0; i--) {
-				PNode each = (PNode) nodeStack.get(i);
-				if (each instanceof PCamera) {
-					bottomCamera = (PCamera) each;
-					return bottomCamera;
-				}				
-			}			
-		}
-		return bottomCamera;
-	}
-	
-	public PStack getNodeStackReference() {
-		return nodeStack;
-	}
-	
-	//****************************************************************
-	// Path Transform 
-	//****************************************************************
+    public boolean acceptsNode(PNode node) {
+        if (excludedNodes != null) {
+            return !excludedNodes.containsKey(node);
+        }
+        return true;
+    }
 
-	public double getScale() {
-		PTS[0] = 0;//x1
-		PTS[1] = 0;//y1
-		PTS[2] = 1;//x2
-		PTS[3] = 0;//y2
-		
-		int count = transformStack.size();
-		for (int i = 0; i < count; i++) {
-			PAffineTransform each = ((PTuple)transformStack.get(i)).transform;
-			if (each != null)
-				each.transform(PTS, 0, PTS, 0, 2);
-		}
-		
-		return Point2D.distance(PTS[0], PTS[1], PTS[2], PTS[3]);		
-	}
+    // ****************************************************************
+    // Picked Nodes
+    // ****************************************************************
 
-	public void pushTransform(PAffineTransform aTransform) {
-		transformStack.push(new PTuple(getPickedNode(), aTransform));
-		if (aTransform != null) {
-			Rectangle2D newPickBounds = (Rectangle2D) getPickBounds().clone();
-			aTransform.inverseTransform(newPickBounds, newPickBounds);
-			pickBoundsStack.push(newPickBounds);
-		}
-	}
+    public void pushNode(PNode aNode) {
+        nodeStack.push(aNode);
+    }
 
-	public void popTransform(PAffineTransform aTransform) {
-		transformStack.pop();
-		if (aTransform != null) {
-			pickBoundsStack.pop();
-		}
-	}
+    public void popNode(PNode aNode) {
+        nodeStack.pop();
+    }
 
-	public PAffineTransform getPathTransformTo(PNode nodeOnPath) {
-		PAffineTransform aTransform = new PAffineTransform();
-		
-		int count = transformStack.size();
-		for (int i = 0; i < count; i++) {
-			PTuple each = (PTuple) transformStack.get(i);
-			if (each.transform != null) aTransform.concatenate(each.transform);
-			if (nodeOnPath == each.node) {
-				return aTransform;
-			}
-		}
-		
-		throw new RuntimeException("Node could not be found on pick path");
-	}
-	
-	//****************************************************************
-	// Process Events - Give each node in the pick path, starting at 
-	// the bottom most one, a chance to handle the event.
-	//****************************************************************
-	
-	public void processEvent(PInputEvent aEvent, int type) {
-		aEvent.setPath(this);
-		
-		for (int i = nodeStack.size() - 1; i >= 0; i--) {
-			PNode each = (PNode) nodeStack.get(i);
+    /**
+     * Get the bottom node on the pick path node stack. That is the last node to
+     * be picked.
+     */
+    public PNode getPickedNode() {
+        return (PNode) nodeStack.peek();
+    }
 
-			EventListenerList list = each.getListenerList();
-			
-			if (list != null) {
-				Object[] listeners = list.getListeners(PInputEventListener.class);
-				
-				for (int j = 0; j < listeners.length; j++) {
-					PInputEventListener listener = (PInputEventListener) listeners[j];
-					listener.processEvent(aEvent, type);					
-				}
-			}			
-		}			
-	}
-		
-	//****************************************************************
-	// Transforming Geometry - Methods to transform geometry through
-	// this path. 
-	//  
-	// Note that this is different that just using the
-	// PNode.localToGlobal (an other coord system transform methods). 
-	// The PNode coord system transform methods always go directly up
-	// through their parents. The PPickPath coord system transform
-	// methods go up through the list of picked nodes instead. And since
-	// cameras can pick their layers in addition to their children these
-	// two paths may be different.
-	//****************************************************************
-	
-	/**
-	 * Convert the given point from the canvas coordinates, down through
-	 * the pick path (and through any camera view transforms applied to the
-	 * path) to the local coordinates of the given node.
-	 */
-	public Point2D canvasToLocal(Point2D canvasPoint, PNode nodeOnPath) {
-		try {
-			return getPathTransformTo(nodeOnPath).inverseTransform(canvasPoint, canvasPoint);		
-		} catch (NoninvertibleTransformException e) {
-			e.printStackTrace();
-		}
-		return null;
-	}
+    // ****************************************************************
+    // Iterating over picked nodes.
+    // ****************************************************************
 
-	/**
-	 * Convert the given dimension from the canvas coordinates, down through
-	 * the pick path (and through any camera view transforms applied to the
-	 * path) to the local coordinates of the given node.
-	 */
-	public Dimension2D canvasToLocal(Dimension2D canvasDimension, PNode nodeOnPath) {
-		return getPathTransformTo(nodeOnPath).inverseTransform(canvasDimension, canvasDimension);
-	}
+    /**
+     * Return the next node that will be picked after the current picked node.
+     * For instance of you have two overlaping children nodes then the topmost
+     * child will always be picked first, use this method to find the covered
+     * child. Return the camera when no more visual will be picked.
+     */
+    public PNode nextPickedNode() {
+        PNode picked = getPickedNode();
 
-	/**
-	 * Convert the given rectangle from the canvas coordinates, down through
-	 * the pick path (and through any camera view transforms applied to the
-	 * path) to the local coordinates of the given node.
-	 */
-	public Rectangle2D canvasToLocal(Rectangle2D canvasRectangle, PNode nodeOnPath) {
-		return getPathTransformTo(nodeOnPath).inverseTransform(canvasRectangle, canvasRectangle);
-	}	
-	
-	/**
-	 * Used to associated nodes with their transforms on the transform stack.
-	 */
-	private static class PTuple {
-		public PNode node;
-		public PAffineTransform transform;
-		
-		public PTuple(PNode n, PAffineTransform t) {
-			node = n;
-			transform = t;
-		}
-	}
+        if (picked == topCamera)
+            return null;
+        if (excludedNodes == null)
+            excludedNodes = new HashMap();
+
+        // exclude current picked node
+        excludedNodes.put(picked, picked);
+
+        Object screenPickBounds = pickBoundsStack.get(0);
+
+        // reset path state
+        pickBoundsStack = new PStack();
+        nodeStack = new PStack();
+        transformStack = new PStack();
+        pickBoundsStack = new PStack();
+
+        pickBoundsStack.push(screenPickBounds);
+
+        // pick again
+        topCamera.fullPick(this);
+
+        // make sure top camera is pushed.
+        if (getNodeStackReference().size() == 0) {
+            pushNode(topCamera);
+            pushTransform(topCamera.getTransformReference(false));
+        }
+
+        return getPickedNode();
+    }
+
+    /**
+     * Get the top camera on the pick path. This is the camera that originated
+     * the pick action.
+     */
+    public PCamera getTopCamera() {
+        return topCamera;
+    }
+
+    /**
+     * Get the bottom camera on the pick path. This may be different then the
+     * top camera if internal cameras are in use.
+     */
+    public PCamera getBottomCamera() {
+        if (bottomCamera == null) {
+            for (int i = nodeStack.size() - 1; i >= 0; i--) {
+                PNode each = (PNode) nodeStack.get(i);
+                if (each instanceof PCamera) {
+                    bottomCamera = (PCamera) each;
+                    return bottomCamera;
+                }
+            }
+        }
+        return bottomCamera;
+    }
+
+    public PStack getNodeStackReference() {
+        return nodeStack;
+    }
+
+    // ****************************************************************
+    // Path Transform
+    // ****************************************************************
+
+    public double getScale() {
+        PTS[0] = 0;// x1
+        PTS[1] = 0;// y1
+        PTS[2] = 1;// x2
+        PTS[3] = 0;// y2
+
+        int count = transformStack.size();
+        for (int i = 0; i < count; i++) {
+            PAffineTransform each = ((PTuple) transformStack.get(i)).transform;
+            if (each != null)
+                each.transform(PTS, 0, PTS, 0, 2);
+        }
+
+        return Point2D.distance(PTS[0], PTS[1], PTS[2], PTS[3]);
+    }
+
+    public void pushTransform(PAffineTransform aTransform) {
+        transformStack.push(new PTuple(getPickedNode(), aTransform));
+        if (aTransform != null) {
+            Rectangle2D newPickBounds = (Rectangle2D) getPickBounds().clone();
+            aTransform.inverseTransform(newPickBounds, newPickBounds);
+            pickBoundsStack.push(newPickBounds);
+        }
+    }
+
+    public void popTransform(PAffineTransform aTransform) {
+        transformStack.pop();
+        if (aTransform != null) {
+            pickBoundsStack.pop();
+        }
+    }
+
+    public PAffineTransform getPathTransformTo(PNode nodeOnPath) {
+        PAffineTransform aTransform = new PAffineTransform();
+
+        int count = transformStack.size();
+        for (int i = 0; i < count; i++) {
+            PTuple each = (PTuple) transformStack.get(i);
+            if (each.transform != null)
+                aTransform.concatenate(each.transform);
+            if (nodeOnPath == each.node) {
+                return aTransform;
+            }
+        }
+
+        throw new RuntimeException("Node could not be found on pick path");
+    }
+
+    // ****************************************************************
+    // Process Events - Give each node in the pick path, starting at
+    // the bottom most one, a chance to handle the event.
+    // ****************************************************************
+
+    public void processEvent(PInputEvent aEvent, int type) {
+        aEvent.setPath(this);
+
+        for (int i = nodeStack.size() - 1; i >= 0; i--) {
+            PNode each = (PNode) nodeStack.get(i);
+
+            EventListenerList list = each.getListenerList();
+
+            if (list != null) {
+                Object[] listeners = list.getListeners(PInputEventListener.class);
+
+                for (int j = 0; j < listeners.length; j++) {
+                    PInputEventListener listener = (PInputEventListener) listeners[j];
+                    listener.processEvent(aEvent, type);
+                }
+            }
+        }
+    }
+
+    // ****************************************************************
+    // Transforming Geometry - Methods to transform geometry through
+    // this path.
+    //  
+    // Note that this is different that just using the
+    // PNode.localToGlobal (an other coord system transform methods).
+    // The PNode coord system transform methods always go directly up
+    // through their parents. The PPickPath coord system transform
+    // methods go up through the list of picked nodes instead. And since
+    // cameras can pick their layers in addition to their children these
+    // two paths may be different.
+    // ****************************************************************
+
+    /**
+     * Convert the given point from the canvas coordinates, down through the
+     * pick path (and through any camera view transforms applied to the path) to
+     * the local coordinates of the given node.
+     */
+    public Point2D canvasToLocal(Point2D canvasPoint, PNode nodeOnPath) {
+        try {
+            return getPathTransformTo(nodeOnPath).inverseTransform(canvasPoint, canvasPoint);
+        }
+        catch (NoninvertibleTransformException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * Convert the given dimension from the canvas coordinates, down through the
+     * pick path (and through any camera view transforms applied to the path) to
+     * the local coordinates of the given node.
+     */
+    public Dimension2D canvasToLocal(Dimension2D canvasDimension, PNode nodeOnPath) {
+        return getPathTransformTo(nodeOnPath).inverseTransform(canvasDimension, canvasDimension);
+    }
+
+    /**
+     * Convert the given rectangle from the canvas coordinates, down through the
+     * pick path (and through any camera view transforms applied to the path) to
+     * the local coordinates of the given node.
+     */
+    public Rectangle2D canvasToLocal(Rectangle2D canvasRectangle, PNode nodeOnPath) {
+        return getPathTransformTo(nodeOnPath).inverseTransform(canvasRectangle, canvasRectangle);
+    }
+
+    /**
+     * Used to associated nodes with their transforms on the transform stack.
+     */
+    private static class PTuple {
+        public PNode node;
+        public PAffineTransform transform;
+
+        public PTuple(PNode n, PAffineTransform t) {
+            node = n;
+            transform = t;
+        }
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/util/PStack.java b/core/src/main/java/edu/umd/cs/piccolo/util/PStack.java
index 100a973..26b1904 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/util/PStack.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/util/PStack.java
@@ -32,32 +32,33 @@
 import java.util.ArrayList;
 
 /**
- * PStack this class should be removed when a non thread safe stack is added
- * to the java class libraries.
+ * PStack this class should be removed when a non thread safe stack is
+ * added to the java class libraries.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PStack extends ArrayList {
-	
-	public PStack() {
-	}
-	
-	public void push(Object o) {
-		add(o);
-	}
-	
-	public Object peek() {
-		int s = size();
-		if (s == 0) {
-			return null;
-		} else {
-			return get(s - 1);
-		}
-	}
-	
-	public Object pop() {
-		return remove(size() - 1);
-	}	
-}
 
+    public PStack() {
+    }
+
+    public void push(Object o) {
+        add(o);
+    }
+
+    public Object peek() {
+        int s = size();
+        if (s == 0) {
+            return null;
+        }
+        else {
+            return get(s - 1);
+        }
+    }
+
+    public Object pop() {
+        return remove(size() - 1);
+    }
+}
diff --git a/core/src/main/java/edu/umd/cs/piccolo/util/PUtil.java b/core/src/main/java/edu/umd/cs/piccolo/util/PUtil.java
index 45c32d9..b44cd28 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/util/PUtil.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/util/PUtil.java
@@ -49,185 +49,206 @@
 /**
  * PUtil util methods for the Piccolo framework.
  *  
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PUtil {
-	
-	public static Iterator NULL_ITERATOR = Collections.EMPTY_LIST.iterator();
-	public static Enumeration NULL_ENUMERATION = new Enumeration() {
-        public boolean hasMoreElements() { return false; }
-        public Object nextElement() { return null; }
-	};
-	public static long DEFAULT_ACTIVITY_STEP_RATE = 20;
-	public static int ACTIVITY_SCHEDULER_FRAME_DELAY = 10;
-		
-	public static OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
-		public void close() { }
-		public void flush() { }
-		public void write(byte[] b) { }
-		public void write(byte[] b, int off, int len) { }
-		public void write(int b) { }
-	};	
-	
-	public static PCamera createBasicScenegraph() {
-		PRoot r = new PRoot();
-		PLayer l = new PLayer();
-		PCamera c = new PCamera();
-		
-		r.addChild(c); 
-		r.addChild(l); 
-		c.addLayer(l);
-		
-		return c;		
-	}
 
-	public static void writeStroke(Stroke aStroke, ObjectOutputStream out) throws IOException {
-		if (aStroke instanceof Serializable) {
-			out.writeBoolean(true);
-			out.writeBoolean(true);
-			out.writeObject(aStroke);
-		} else if (aStroke instanceof BasicStroke) {
-			out.writeBoolean(true);
-			out.writeBoolean(false);
-			BasicStroke s = (BasicStroke) aStroke;
-			
-			float[] dash = s.getDashArray();
-			
-			if (dash == null) {
-				out.write(0);
-			} else {
-				out.write(dash.length);
-				for (int i = 0; i < dash.length; i++) {
-					out.writeFloat(dash[i]);
-				}
-			}
-						
-			out.writeFloat(s.getLineWidth());
-			out.writeInt(s.getEndCap());
-			out.writeInt(s.getLineJoin());
-			out.writeFloat(s.getMiterLimit());			
-			out.writeFloat(s.getDashPhase());
-		} else {
-			out.writeBoolean(false);
-		}
-	}
-	
-	public static Stroke readStroke(ObjectInputStream in) throws IOException, ClassNotFoundException {
-		boolean wroteStroke = in.readBoolean();
-		if (wroteStroke) {
-			boolean serializedStroke = in.readBoolean();
-			if (serializedStroke) {
-				return (Stroke) in.readObject();
-			} else {
-				float[] dash = null;
-				int dashLength = in.read();
-				
-				if (dashLength != 0) {
-					dash = new float[dashLength];
-					for (int i = 0; i < dashLength; i++) {
-						dash[i] = in.readFloat();
-					}
-				}
-				
-				float lineWidth = in.readFloat();
-				int endCap = in.readInt();
-				int lineJoin = in.readInt();
-				float miterLimit = in.readFloat();
-				float dashPhase = in.readFloat();
-				
-				return new BasicStroke(lineWidth, endCap, lineJoin, miterLimit, dash, dashPhase);
-			}
-		} else {
-			return null;
-		}
-	}
-	
-	private static final int PATH_IS_DONE = -1;
+    public static Iterator NULL_ITERATOR = Collections.EMPTY_LIST.iterator();
+    public static Enumeration NULL_ENUMERATION = new Enumeration() {
+        public boolean hasMoreElements() {
+            return false;
+        }
 
-	public static GeneralPath readPath(ObjectInputStream in) throws IOException, ClassNotFoundException {
-		GeneralPath path = new GeneralPath();
-	
-		while(true) {
-			int segType = in.readInt();
-		
-			switch(segType) {
-				case PathIterator.SEG_MOVETO:
-					path.moveTo(in.readFloat(), in.readFloat());
-					break;
-				
-				case PathIterator.SEG_LINETO:
-					path.lineTo(in.readFloat(), in.readFloat());
-					break;
-				
-				case PathIterator.SEG_QUADTO:
-					path.quadTo(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
-					break;
-				
-				case PathIterator.SEG_CUBICTO:
-					path.curveTo(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
-					break;
-				
-				case PathIterator.SEG_CLOSE:
-					path.closePath();
-					break;
-				
-				case PATH_IS_DONE:
-					return path;
+        public Object nextElement() {
+            return null;
+        }
+    };
+    public static long DEFAULT_ACTIVITY_STEP_RATE = 20;
+    public static int ACTIVITY_SCHEDULER_FRAME_DELAY = 10;
 
-				default:
-					throw new IOException();
-			}
-		}
-	}
-	
-	public static void writePath(GeneralPath path, ObjectOutputStream out) throws IOException {		
-		PathIterator i = path.getPathIterator(null);
-		float[] data = new float[6];
-	
-		while(!i.isDone()) {
-			switch(i.currentSegment(data)) {
-				case PathIterator.SEG_MOVETO:
-					out.writeInt(PathIterator.SEG_MOVETO);
-					out.writeFloat(data[0]);
-					out.writeFloat(data[1]);
-					break;
-	
-				case PathIterator.SEG_LINETO:
-					out.writeInt(PathIterator.SEG_LINETO);
-					out.writeFloat(data[0]);
-					out.writeFloat(data[1]);
-					break;
-	
-				case PathIterator.SEG_QUADTO:
-					out.writeInt(PathIterator.SEG_QUADTO);
-					out.writeFloat(data[0]);
-					out.writeFloat(data[1]);
-					out.writeFloat(data[2]);
-					out.writeFloat(data[3]);
-					break;
-						
-				case PathIterator.SEG_CUBICTO:
-					out.writeInt(PathIterator.SEG_CUBICTO);
-					out.writeFloat(data[0]);
-					out.writeFloat(data[1]);
-					out.writeFloat(data[2]);
-					out.writeFloat(data[3]);
-					out.writeFloat(data[4]);
-					out.writeFloat(data[5]);				
-					break;
-						
-				case PathIterator.SEG_CLOSE :
-					out.writeInt(PathIterator.SEG_CLOSE);					
-					break;
-						
-				default :
-					throw new IOException();
-			}
-	
-			i.next();
-		}
-			
-		out.writeInt(PATH_IS_DONE);
-	}
+    public static OutputStream NULL_OUTPUT_STREAM = new OutputStream() {
+        public void close() {
+        }
+
+        public void flush() {
+        }
+
+        public void write(byte[] b) {
+        }
+
+        public void write(byte[] b, int off, int len) {
+        }
+
+        public void write(int b) {
+        }
+    };
+
+    public static PCamera createBasicScenegraph() {
+        PRoot r = new PRoot();
+        PLayer l = new PLayer();
+        PCamera c = new PCamera();
+
+        r.addChild(c);
+        r.addChild(l);
+        c.addLayer(l);
+
+        return c;
+    }
+
+    public static void writeStroke(Stroke aStroke, ObjectOutputStream out) throws IOException {
+        if (aStroke instanceof Serializable) {
+            out.writeBoolean(true);
+            out.writeBoolean(true);
+            out.writeObject(aStroke);
+        }
+        else if (aStroke instanceof BasicStroke) {
+            out.writeBoolean(true);
+            out.writeBoolean(false);
+            BasicStroke s = (BasicStroke) aStroke;
+
+            float[] dash = s.getDashArray();
+
+            if (dash == null) {
+                out.write(0);
+            }
+            else {
+                out.write(dash.length);
+                for (int i = 0; i < dash.length; i++) {
+                    out.writeFloat(dash[i]);
+                }
+            }
+
+            out.writeFloat(s.getLineWidth());
+            out.writeInt(s.getEndCap());
+            out.writeInt(s.getLineJoin());
+            out.writeFloat(s.getMiterLimit());
+            out.writeFloat(s.getDashPhase());
+        }
+        else {
+            out.writeBoolean(false);
+        }
+    }
+
+    public static Stroke readStroke(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        boolean wroteStroke = in.readBoolean();
+        if (wroteStroke) {
+            boolean serializedStroke = in.readBoolean();
+            if (serializedStroke) {
+                return (Stroke) in.readObject();
+            }
+            else {
+                float[] dash = null;
+                int dashLength = in.read();
+
+                if (dashLength != 0) {
+                    dash = new float[dashLength];
+                    for (int i = 0; i < dashLength; i++) {
+                        dash[i] = in.readFloat();
+                    }
+                }
+
+                float lineWidth = in.readFloat();
+                int endCap = in.readInt();
+                int lineJoin = in.readInt();
+                float miterLimit = in.readFloat();
+                float dashPhase = in.readFloat();
+
+                return new BasicStroke(lineWidth, endCap, lineJoin, miterLimit, dash, dashPhase);
+            }
+        }
+        else {
+            return null;
+        }
+    }
+
+    private static final int PATH_IS_DONE = -1;
+
+    public static GeneralPath readPath(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        GeneralPath path = new GeneralPath();
+
+        while (true) {
+            int segType = in.readInt();
+
+            switch (segType) {
+                case PathIterator.SEG_MOVETO:
+                    path.moveTo(in.readFloat(), in.readFloat());
+                    break;
+
+                case PathIterator.SEG_LINETO:
+                    path.lineTo(in.readFloat(), in.readFloat());
+                    break;
+
+                case PathIterator.SEG_QUADTO:
+                    path.quadTo(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
+                    break;
+
+                case PathIterator.SEG_CUBICTO:
+                    path.curveTo(in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat(), in
+                            .readFloat());
+                    break;
+
+                case PathIterator.SEG_CLOSE:
+                    path.closePath();
+                    break;
+
+                case PATH_IS_DONE:
+                    return path;
+
+                default:
+                    throw new IOException();
+            }
+        }
+    }
+
+    public static void writePath(GeneralPath path, ObjectOutputStream out) throws IOException {
+        PathIterator i = path.getPathIterator(null);
+        float[] data = new float[6];
+
+        while (!i.isDone()) {
+            switch (i.currentSegment(data)) {
+                case PathIterator.SEG_MOVETO:
+                    out.writeInt(PathIterator.SEG_MOVETO);
+                    out.writeFloat(data[0]);
+                    out.writeFloat(data[1]);
+                    break;
+
+                case PathIterator.SEG_LINETO:
+                    out.writeInt(PathIterator.SEG_LINETO);
+                    out.writeFloat(data[0]);
+                    out.writeFloat(data[1]);
+                    break;
+
+                case PathIterator.SEG_QUADTO:
+                    out.writeInt(PathIterator.SEG_QUADTO);
+                    out.writeFloat(data[0]);
+                    out.writeFloat(data[1]);
+                    out.writeFloat(data[2]);
+                    out.writeFloat(data[3]);
+                    break;
+
+                case PathIterator.SEG_CUBICTO:
+                    out.writeInt(PathIterator.SEG_CUBICTO);
+                    out.writeFloat(data[0]);
+                    out.writeFloat(data[1]);
+                    out.writeFloat(data[2]);
+                    out.writeFloat(data[3]);
+                    out.writeFloat(data[4]);
+                    out.writeFloat(data[5]);
+                    break;
+
+                case PathIterator.SEG_CLOSE:
+                    out.writeInt(PathIterator.SEG_CLOSE);
+                    break;
+
+                default:
+                    throw new IOException();
+            }
+
+            i.next();
+        }
+
+        out.writeInt(PATH_IS_DONE);
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/PCameraTest.java b/core/src/test/java/edu/umd/cs/piccolo/PCameraTest.java
index bbb97a0..30f2223 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/PCameraTest.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/PCameraTest.java
@@ -1,37 +1,38 @@
 package edu.umd.cs.piccolo;
+
 import junit.framework.TestCase;
 
 public class PCameraTest extends TestCase {
-	
-	public PCameraTest(String name) {
-		super(name);
-	}
-	
-	public void testCopy() {
-		PNode n = new PNode();
-		
-		PLayer layer1 = new PLayer();
-		PLayer layer2 = new PLayer();
-		
-		PCamera camera1 = new PCamera();
-		PCamera camera2 = new PCamera();
 
-		n.addChild(layer1); 			
-		n.addChild(layer2); 			
-		n.addChild(camera1);
-		n.addChild(camera2);				
-		
-		camera1.addLayer(layer1);
-		camera1.addLayer(layer2);
-		camera2.addLayer(layer1);
-		camera2.addLayer(layer2);
+    public PCameraTest(String name) {
+        super(name);
+    }
 
-		// no layers should be written out since they are written conditionally.
-		PCamera cameraCopy = (PCamera) camera1.clone();
-		assertEquals(cameraCopy.getLayerCount(), 0);
-		
-		n.clone();
-		assertEquals(((PCamera)n.getChildrenReference().get(2)).getLayerCount(), 2);						
-		assertEquals(((PLayer)n.getChildrenReference().get(1)).getCameraCount(), 2);				
-	}	
+    public void testCopy() {
+        PNode n = new PNode();
+
+        PLayer layer1 = new PLayer();
+        PLayer layer2 = new PLayer();
+
+        PCamera camera1 = new PCamera();
+        PCamera camera2 = new PCamera();
+
+        n.addChild(layer1);
+        n.addChild(layer2);
+        n.addChild(camera1);
+        n.addChild(camera2);
+
+        camera1.addLayer(layer1);
+        camera1.addLayer(layer2);
+        camera2.addLayer(layer1);
+        camera2.addLayer(layer2);
+
+        // no layers should be written out since they are written conditionally.
+        PCamera cameraCopy = (PCamera) camera1.clone();
+        assertEquals(cameraCopy.getLayerCount(), 0);
+
+        n.clone();
+        assertEquals(((PCamera) n.getChildrenReference().get(2)).getLayerCount(), 2);
+        assertEquals(((PLayer) n.getChildrenReference().get(1)).getCameraCount(), 2);
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/PNodeTest.java b/core/src/test/java/edu/umd/cs/piccolo/PNodeTest.java
index 3481dc4..3c549b2 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/PNodeTest.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/PNodeTest.java
@@ -1,4 +1,5 @@
 package edu.umd.cs.piccolo;
+
 import java.awt.Color;
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
@@ -12,219 +13,217 @@
 
 public class PNodeTest extends TestCase {
 
-	public PNodeTest(String name) {
-		super(name);
-	}
-	
-	public void setUp() {
-	}
-	
-	public void testCenterBaseBoundsOnPoint() {
-		PNode aNode = new PNode();
+    public PNodeTest(String name) {
+        super(name);
+    }
 
-		aNode.setBounds(100, 300, 100, 80);
-		aNode.centerBoundsOnPoint(0, 0);
-		assertEquals(-50, aNode.getBoundsReference().getX(), 0);
-		assertEquals(-40, aNode.getBoundsReference().getY(), 0);
-	}
-		
-	public void testClientProperties() {
-		PNode n = new PNode();
-		
-		assertNull(n.getAttribute(null));
-		n.addAttribute("a", "b");
-		assertEquals(n.getAttribute("a"), "b");
-		assertNull(n.getAttribute(null));
-		n.addAttribute("a", null);
-		assertNull(n.getAttribute("a"));
-	}
-	
-	public void testFullScale() {
-		PNode aParent = new PNode();
-		PNode aNode = new PNode();
+    public void setUp() {
+    }
 
-		aParent.addChild(aNode);
+    public void testCenterBaseBoundsOnPoint() {
+        PNode aNode = new PNode();
 
-		aParent.scale(2.0);
-		aNode.scale(0.5);
+        aNode.setBounds(100, 300, 100, 80);
+        aNode.centerBoundsOnPoint(0, 0);
+        assertEquals(-50, aNode.getBoundsReference().getX(), 0);
+        assertEquals(-40, aNode.getBoundsReference().getY(), 0);
+    }
 
-		
-		assertEquals(1.0, aNode.getGlobalScale(), 0);
+    public void testClientProperties() {
+        PNode n = new PNode();
 
-		aParent.setScale(1.0);
-		assertEquals(0.5, aNode.getGlobalScale(), 0);
+        assertNull(n.getAttribute(null));
+        n.addAttribute("a", "b");
+        assertEquals(n.getAttribute("a"), "b");
+        assertNull(n.getAttribute(null));
+        n.addAttribute("a", null);
+        assertNull(n.getAttribute("a"));
+    }
 
-		aNode.setScale(.75);
-		assertEquals(0.75, aNode.getGlobalScale(), 0);
-	}
-	
-	public void testReparent() {
-		PNode aParent = new PNode();
-		PNode aNode = new PNode();
+    public void testFullScale() {
+        PNode aParent = new PNode();
+        PNode aNode = new PNode();
 
-		aParent.setOffset(400, 500);
-		aParent.scale(0.5);
-		aNode.reparent(aParent);
-		
-		assertEquals(0, aNode.getGlobalTranslation().getX(), 0);
-		assertEquals(0, aNode.getGlobalTranslation().getY(), 0);
-		assertEquals(2.0, aNode.getScale(), 0);
-		
-		aNode.setGlobalScale(0.25);
-		aNode.setGlobalTranslation(new Point2D.Double(10, 10));
-		
-		assertEquals(10, aNode.getGlobalTranslation().getX(), 0);
-		assertEquals(10, aNode.getGlobalTranslation().getY(), 0);
-		assertEquals(0.25, aNode.getGlobalScale(), 0);
-	}
-	
-	public void testFindIntersectingNodes() {
-		PNode n = new PNode();
-		PNode c = new PNode();
+        aParent.addChild(aNode);
 
-		n.addChild(c);
-		n.setBounds(0, 0, 100, 100);
-		c.setBounds(0, 0, 100, 100);
-		c.scale(200);
-		
-		ArrayList found = new ArrayList();
-		Rectangle2D rect2d = new Rectangle2D.Double(50, 50, 10, 10);
-		n.findIntersectingNodes(rect2d, found);
-		
-		assertEquals(found.size(), 2);
-		assertEquals(rect2d.getHeight(), 10, 0);
-		found = new ArrayList();
-		
-		PBounds bounds = new PBounds(50, 50, 10, 10);
-		n.findIntersectingNodes(bounds, found);
+        aParent.scale(2.0);
+        aNode.scale(0.5);
 
-		assertEquals(found.size(), 2);
-		assertEquals(bounds.getHeight(), 10, 0);
-	}
-	
-	public void testRemoveNonexistantListener() {
-		PNode n = new PNode();
-		n.removeInputEventListener(new PBasicInputEventHandler());
-	}
-	
-	public void testAddChild() {
-		PNode p = new PNode();
-		PNode c = new PNode();
-		
-		p.addChild(c);
-		p.addChild(new PNode());
-		p.addChild(new PNode());
-		
-		p.addChild(c);
-		assertEquals(c, p.getChild(2));
+        assertEquals(1.0, aNode.getGlobalScale(), 0);
 
-		p.addChild(0, c);
-		assertEquals(c, p.getChild(0)); 
-		
-		p.addChild(1, c);
-		assertEquals(c, p.getChild(1)); 
+        aParent.setScale(1.0);
+        assertEquals(0.5, aNode.getGlobalScale(), 0);
 
-		p.addChild(2, c);
-		assertEquals(c, p.getChild(2)); 		
-	}
-	
-	public void testCopy() {
-		PNode aNode = new PNode();		
-		aNode.setPaint(Color.yellow);
+        aNode.setScale(.75);
+        assertEquals(0.75, aNode.getGlobalScale(), 0);
+    }
 
-		PNode aChild = new PNode();
-		aNode.addChild(aChild);
-				
-		aNode = (PNode) aNode.clone();
-		
-		assertEquals(aNode.getPaint(), Color.yellow);
-		assertEquals(aNode.getChildrenCount(), 1);
-	}
+    public void testReparent() {
+        PNode aParent = new PNode();
+        PNode aNode = new PNode();
 
-	public void testLocalToGlobal() {
-		PNode aParent = new PNode();
-		PNode aChild = new PNode(); 	
+        aParent.setOffset(400, 500);
+        aParent.scale(0.5);
+        aNode.reparent(aParent);
 
-		aParent.addChild(aChild);
-		aChild.scale(0.5);
+        assertEquals(0, aNode.getGlobalTranslation().getX(), 0);
+        assertEquals(0, aNode.getGlobalTranslation().getY(), 0);
+        assertEquals(2.0, aNode.getScale(), 0);
 
-		// bounds
-		PBounds bnds = new PBounds(0, 0, 50, 50);
-		
-		aChild.localToGlobal(bnds);
-		assertEquals(0, bnds.x, 0);
-		assertEquals(0, bnds.y, 0);
-		assertEquals(25, bnds.width, 0);
-		assertEquals(25, bnds.height, 0);
+        aNode.setGlobalScale(0.25);
+        aNode.setGlobalTranslation(new Point2D.Double(10, 10));
 
-		aChild.globalToLocal(bnds);
-		assertEquals(0, bnds.x, 0);
-		assertEquals(0, bnds.y, 0);
-		assertEquals(50, bnds.width, 0);
-		assertEquals(50, bnds.height, 0);
+        assertEquals(10, aNode.getGlobalTranslation().getX(), 0);
+        assertEquals(10, aNode.getGlobalTranslation().getY(), 0);
+        assertEquals(0.25, aNode.getGlobalScale(), 0);
+    }
 
-		aChild.getGlobalToLocalTransform(new PAffineTransform());
-		aChild.getLocalToGlobalTransform(new PAffineTransform()).createTransformedShape(aChild.getBounds());
+    public void testFindIntersectingNodes() {
+        PNode n = new PNode();
+        PNode c = new PNode();
 
-		// dimensions	
-		PDimension dim = new PDimension(50, 50);
+        n.addChild(c);
+        n.setBounds(0, 0, 100, 100);
+        c.setBounds(0, 0, 100, 100);
+        c.scale(200);
 
-		aChild.localToGlobal(dim);
-		assertEquals(25, dim.getHeight(), 0);
-		assertEquals(25, dim.getWidth(), 0);
+        ArrayList found = new ArrayList();
+        Rectangle2D rect2d = new Rectangle2D.Double(50, 50, 10, 10);
+        n.findIntersectingNodes(rect2d, found);
 
-	
-		aChild.globalToLocal(dim);
-		assertEquals(50, dim.getHeight(), 0);
-		assertEquals(50, dim.getWidth(), 0);
-	}
-	
-	public void testToString() {
-		PNode a = new PNode();
-		PNode b = new PNode();
-		PNode c = new PNode();
-		PNode d = new PNode();
-		PNode e = new PNode();
-		PNode f = new PNode();
+        assertEquals(found.size(), 2);
+        assertEquals(rect2d.getHeight(), 10, 0);
+        found = new ArrayList();
 
-		a.translate(100, 100);
-		a.getFullBoundsReference();
-		
-		a.addChild(b);
-		b.addChild(c);
-		c.addChild(d);
-		d.addChild(e);
-		e.addChild(f);
-		
-		assertNotNull(a.toString());
-	}
-	
-	public void testRecursiveLayout() {
-		PNode layoutNode1 = new PNode() {
-			protected void layoutChildren() {
-				if (getChildrenCount() > 0) {
-					getChild(0).setOffset(1, 0);
-				}
-			}
-		};
+        PBounds bounds = new PBounds(50, 50, 10, 10);
+        n.findIntersectingNodes(bounds, found);
 
-		PNode layoutNode2 = new PNode() {
-			protected void layoutChildren() {
-				if (getChildrenCount() > 0) {
-					getChild(0).setOffset(1, 0);
-				}
-			}
-		};
-		
-		layoutNode1.addChild(layoutNode2);
-		
-		PNode n = new PNode();
-		n.setBounds(0, 0, 100, 100);
-		
-		layoutNode2.addChild(n);
-		
-		n.setBounds(10, 10, 100, 100);
-		
-		layoutNode1.getFullBoundsReference();
-	}
+        assertEquals(found.size(), 2);
+        assertEquals(bounds.getHeight(), 10, 0);
+    }
+
+    public void testRemoveNonexistantListener() {
+        PNode n = new PNode();
+        n.removeInputEventListener(new PBasicInputEventHandler());
+    }
+
+    public void testAddChild() {
+        PNode p = new PNode();
+        PNode c = new PNode();
+
+        p.addChild(c);
+        p.addChild(new PNode());
+        p.addChild(new PNode());
+
+        p.addChild(c);
+        assertEquals(c, p.getChild(2));
+
+        p.addChild(0, c);
+        assertEquals(c, p.getChild(0));
+
+        p.addChild(1, c);
+        assertEquals(c, p.getChild(1));
+
+        p.addChild(2, c);
+        assertEquals(c, p.getChild(2));
+    }
+
+    public void testCopy() {
+        PNode aNode = new PNode();
+        aNode.setPaint(Color.yellow);
+
+        PNode aChild = new PNode();
+        aNode.addChild(aChild);
+
+        aNode = (PNode) aNode.clone();
+
+        assertEquals(aNode.getPaint(), Color.yellow);
+        assertEquals(aNode.getChildrenCount(), 1);
+    }
+
+    public void testLocalToGlobal() {
+        PNode aParent = new PNode();
+        PNode aChild = new PNode();
+
+        aParent.addChild(aChild);
+        aChild.scale(0.5);
+
+        // bounds
+        PBounds bnds = new PBounds(0, 0, 50, 50);
+
+        aChild.localToGlobal(bnds);
+        assertEquals(0, bnds.x, 0);
+        assertEquals(0, bnds.y, 0);
+        assertEquals(25, bnds.width, 0);
+        assertEquals(25, bnds.height, 0);
+
+        aChild.globalToLocal(bnds);
+        assertEquals(0, bnds.x, 0);
+        assertEquals(0, bnds.y, 0);
+        assertEquals(50, bnds.width, 0);
+        assertEquals(50, bnds.height, 0);
+
+        aChild.getGlobalToLocalTransform(new PAffineTransform());
+        aChild.getLocalToGlobalTransform(new PAffineTransform()).createTransformedShape(aChild.getBounds());
+
+        // dimensions
+        PDimension dim = new PDimension(50, 50);
+
+        aChild.localToGlobal(dim);
+        assertEquals(25, dim.getHeight(), 0);
+        assertEquals(25, dim.getWidth(), 0);
+
+        aChild.globalToLocal(dim);
+        assertEquals(50, dim.getHeight(), 0);
+        assertEquals(50, dim.getWidth(), 0);
+    }
+
+    public void testToString() {
+        PNode a = new PNode();
+        PNode b = new PNode();
+        PNode c = new PNode();
+        PNode d = new PNode();
+        PNode e = new PNode();
+        PNode f = new PNode();
+
+        a.translate(100, 100);
+        a.getFullBoundsReference();
+
+        a.addChild(b);
+        b.addChild(c);
+        c.addChild(d);
+        d.addChild(e);
+        e.addChild(f);
+
+        assertNotNull(a.toString());
+    }
+
+    public void testRecursiveLayout() {
+        PNode layoutNode1 = new PNode() {
+            protected void layoutChildren() {
+                if (getChildrenCount() > 0) {
+                    getChild(0).setOffset(1, 0);
+                }
+            }
+        };
+
+        PNode layoutNode2 = new PNode() {
+            protected void layoutChildren() {
+                if (getChildrenCount() > 0) {
+                    getChild(0).setOffset(1, 0);
+                }
+            }
+        };
+
+        layoutNode1.addChild(layoutNode2);
+
+        PNode n = new PNode();
+        n.setBounds(0, 0, 100, 100);
+
+        layoutNode2.addChild(n);
+
+        n.setBounds(10, 10, 100, 100);
+
+        layoutNode1.getFullBoundsReference();
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/PerformanceLog.java b/core/src/test/java/edu/umd/cs/piccolo/PerformanceLog.java
index 8e44f65..4bb550c 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/PerformanceLog.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/PerformanceLog.java
@@ -1,61 +1,62 @@
 package edu.umd.cs.piccolo;
+
 import java.util.ArrayList;
 import java.util.Iterator;
 
 public class PerformanceLog {
 
-	private ArrayList log = new ArrayList();
-	private long testTime;
-	
-	public static class ZLogEntry {
-		public String name;
-		public long time;
-		
-		public ZLogEntry(String aName, long aTime) {
-			name = aName;
-			time = aTime;
-		}
-	}
-	
-	public void startTest() {
-		Runtime.getRuntime().gc();
-		testTime = System.currentTimeMillis();
-	}
+    private ArrayList log = new ArrayList();
+    private long testTime;
 
-	public void endTest(String name) {
-		testTime = System.currentTimeMillis() - testTime;
-		addEntry(name, testTime);
-		System.gc();
-	}
+    public static class ZLogEntry {
+        public String name;
+        public long time;
 
-	public void addEntry(String aName, long aTime) {
-		log.add(new ZLogEntry(aName, aTime));
-	}
-	
-	public void clear() {
-		log.clear();
-	}
+        public ZLogEntry(String aName, long aTime) {
+            name = aName;
+            time = aTime;
+        }
+    }
 
-	public void writeLog() {
+    public void startTest() {
+        Runtime.getRuntime().gc();
+        testTime = System.currentTimeMillis();
+    }
 
-		System.out.println();
-		System.out.println("Test data for input into spreadsheet:");
-		System.out.println();
+    public void endTest(String name) {
+        testTime = System.currentTimeMillis() - testTime;
+        addEntry(name, testTime);
+        System.gc();
+    }
 
-		Iterator i = log.iterator();
-		while (i.hasNext()) {
-			ZLogEntry each = (ZLogEntry) i.next();
-			System.out.println(each.time);
-		}
+    public void addEntry(String aName, long aTime) {
+        log.add(new ZLogEntry(aName, aTime));
+    }
 
-		System.out.println();
-		System.out.println("Labled test results, see above for simple column \n of times for input into spreadsheet:");
-		System.out.println();
+    public void clear() {
+        log.clear();
+    }
 
-		i = log.iterator();
-		while (i.hasNext()) {
-			ZLogEntry each = (ZLogEntry) i.next();
-			System.out.println(each.name + ", " + each.time);
-		}
-	}
+    public void writeLog() {
+
+        System.out.println();
+        System.out.println("Test data for input into spreadsheet:");
+        System.out.println();
+
+        Iterator i = log.iterator();
+        while (i.hasNext()) {
+            ZLogEntry each = (ZLogEntry) i.next();
+            System.out.println(each.time);
+        }
+
+        System.out.println();
+        System.out.println("Labled test results, see above for simple column \n of times for input into spreadsheet:");
+        System.out.println();
+
+        i = log.iterator();
+        while (i.hasNext()) {
+            ZLogEntry each = (ZLogEntry) i.next();
+            System.out.println(each.name + ", " + each.time);
+        }
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/PerformanceTests.java b/core/src/test/java/edu/umd/cs/piccolo/PerformanceTests.java
index bb026a2..6f7fb4e 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/PerformanceTests.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/PerformanceTests.java
@@ -1,4 +1,5 @@
 package edu.umd.cs.piccolo;
+
 import java.awt.Graphics2D;
 import java.awt.GraphicsConfiguration;
 import java.awt.GraphicsEnvironment;
@@ -17,342 +18,349 @@
 import edu.umd.cs.piccolo.util.PBounds;
 
 public class PerformanceTests extends TestCase {
-	
-	private static PerformanceLog log = new PerformanceLog();
-	private static int NUMBER_NODES = 20000;
 
-	public PerformanceTests(String name) {
-		super(name);
-	}
-		
-	public void testRunPerformanceTests() {
-		// three times to warm up JVM
-		for (int i = 0; i < 3; i++) {
-			addNodes();
-			copyNodes();
-			createNodes();
-			createPaths();
-			fullIntersectsNodes();
-			memorySizeOfNodes();
-			//removeNodes();
-			translateNodes();
-			costOfNoBoundsCache();
-//			renderSpeed();
-			if (i != 2) {
-				log.clear();
-			}
-		}
-		log.writeLog();
-	}
-	
-	public void createNodes() { 	
-		PNode[] nodes = new PNode[NUMBER_NODES];
+    private static PerformanceLog log = new PerformanceLog();
+    private static int NUMBER_NODES = 20000;
 
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i] = new PNode();
-		}		
-		log.endTest("Create " + NUMBER_NODES + " new nodes");		
-	}
-	
-	public void createPaths() { 	
-		PNode[] nodes = new PNode[NUMBER_NODES];
+    public PerformanceTests(String name) {
+        super(name);
+    }
 
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i] = PPath.createRectangle(0, 0, 100, 80);
-		}		
-		log.endTest("Create " + NUMBER_NODES + " new rect paths");		
-		
-		Random r = new Random();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i].translate(r.nextFloat() * 300, r.nextFloat() * 300);
-		}		
-	}
-	
-	public void addNodes() {
-		PNode parent = new PNode();
-		PNode[] nodes = new PNode[NUMBER_NODES];
+    public void testRunPerformanceTests() {
+        // three times to warm up JVM
+        for (int i = 0; i < 3; i++) {
+            addNodes();
+            copyNodes();
+            createNodes();
+            createPaths();
+            fullIntersectsNodes();
+            memorySizeOfNodes();
+            // removeNodes();
+            translateNodes();
+            costOfNoBoundsCache();
+            // renderSpeed();
+            if (i != 2) {
+                log.clear();
+            }
+        }
+        log.writeLog();
+    }
 
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i] = new PNode();
-		}		
-		
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			parent.addChild(nodes[i]);
-		}		
-		log.endTest("Add " + NUMBER_NODES + " nodes to a new parent");
-	}
-	
-	public void removeNodes() {
-		PNode parent = new PNode();
-		PNode[] nodes = new PNode[NUMBER_NODES];
-		ArrayList list = new ArrayList();
+    public void createNodes() {
+        PNode[] nodes = new PNode[NUMBER_NODES];
 
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i] = new PNode();
-		}		
-		
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			parent.addChild(nodes[i]);
-			list.add(nodes[i]);
-		}		
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i] = new PNode();
+        }
+        log.endTest("Create " + NUMBER_NODES + " new nodes");
+    }
 
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {	
-			parent.removeChild(nodes[i]);	
-		}
-		log.endTest("Remove " + NUMBER_NODES + " nodes using removeChild() front to back");
-		
-		parent.addChildren(list);
-				
-		log.startTest();
-		for (int i = NUMBER_NODES - 1; i >= 0; i--) {	
-			parent.removeChild(i);
-		}
-		log.endTest("Remove " + NUMBER_NODES + " nodes using removeChild() back to front by index");
+    public void createPaths() {
+        PNode[] nodes = new PNode[NUMBER_NODES];
 
-		log.startTest();
-//		for (int i = NUMBER_NODES - 1; i >= 0; i--) {	
-//			parent.removeChild(nodes[i]);	
-//		}
-		log.endTest("Remove " + NUMBER_NODES + " nodes using removeChild() back to front by object, TO_SLOW");
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i] = PPath.createRectangle(0, 0, 100, 80);
+        }
+        log.endTest("Create " + NUMBER_NODES + " new rect paths");
 
-		parent.addChildren(list);
-				
-		log.startTest();
-		parent.removeChildren(list);		
-		log.endTest("Remove " + NUMBER_NODES + " nodes using removeChildren()");
+        Random r = new Random();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i].translate(r.nextFloat() * 300, r.nextFloat() * 300);
+        }
+    }
 
-		parent.addChildren(list);
-				
-		log.startTest();
-		parent.removeAllChildren(); 	
-		log.endTest("Remove " + NUMBER_NODES + " nodes using removeAllChildren()");
-	}
+    public void addNodes() {
+        PNode parent = new PNode();
+        PNode[] nodes = new PNode[NUMBER_NODES];
 
-	public void translateNodes() {
-		PNode parent = new PNode();
-		PNode[] nodes = new PNode[NUMBER_NODES];
-		PBounds b = new PBounds();
-		Random r = new Random();
-		
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i] = new PNode();
-			nodes[i].setBounds(1000 * r.nextFloat(), 1000 * r.nextFloat(), 100, 80);
-			parent.addChild(nodes[i]);
-			nodes[i].getFullBoundsReference();
-		}
-		
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i].translate(1000 * r.nextFloat(), 1000 * r.nextFloat());
-			nodes[i].scale(1000 * r.nextFloat());			
-//			nodes[i].translateBy(100.01, 100.2);
-//			nodes[i].scaleBy(0.9);
-		}		
-		log.endTest("Translate " + NUMBER_NODES + " nodes, not counting repaint or validate layout");
-				
-		log.startTest();
-		//parent.validateFullBounds(); now protected.
-		parent.getFullBoundsReference(); // calls validateFullBounds as a side effect.
-		log.endTest("Validate Layout after translate " + NUMBER_NODES + " nodes");
-		
-		log.startTest();
-		parent.validateFullPaint();
-		log.endTest("Validate Paint after translate " + NUMBER_NODES + " nodes");
-		
-		log.startTest();
-		parent.computeFullBounds(b);
-		log.endTest("Parent compute bounds of " + NUMBER_NODES + " children nodes");
-	}
-	
-	public void fullIntersectsNodes() {
-		PNode parent = new PNode();
-		PNode[] nodes = new PNode[NUMBER_NODES];
-		PBounds b = new PBounds(0, 50, 100, 20);
-		
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i] = new PNode();
-			parent.addChild(nodes[i]);
-		}
-		
-		//parent.validateFullBounds(); // now protected
-		parent.getFullBoundsReference(); // calls validateFullBounds as a side effect.
-		
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i].fullIntersects(b);
-		}		
-		log.endTest("Do fullIntersects test for " + NUMBER_NODES + " nodes");
-	}
-	
-	public void memorySizeOfNodes() {
-		PNode[] nodes = new PNode[NUMBER_NODES];
-		Runtime.getRuntime().gc();
-		long startTotalMemory = Runtime.getRuntime().totalMemory();
-		long startFree = Runtime.getRuntime().freeMemory();
-		long endFree;
-		long endTotal;
-		
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i] = new PNode();
-		}
-		
-		Runtime.getRuntime().gc();
-		endFree = Runtime.getRuntime().freeMemory();
-		endTotal = Runtime.getRuntime().totalMemory();
-		
-		log.addEntry("Approximate k used by " + NUMBER_NODES + " nodes", ((endTotal - startTotalMemory) + (startFree - endFree)) / 1024);
-		nodes[0].getPaint();
-	}
-	
-	public void copyNodes() {
-		PNode parent = new PNode();
-		PNode[] nodes = new PNode[NUMBER_NODES];
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i] = new PNode();
+        }
 
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i] = new PNode();
-			parent.addChild(nodes[i]);
-		}		
-		
-		log.startTest();
-		parent.clone();
-		log.endTest("Copy/Serialize " + NUMBER_NODES + " nodes");		
-	}	
-	
-	public void costOfNoBoundsCache() {
-		PNode[] nodes = new PNode[NUMBER_NODES];
-		PBounds[] bounds = new PBounds[NUMBER_NODES];
-		PBounds pickRect = new PBounds(0, 0, 1, 1);
-		Random r = new Random();
-				
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i] = new PNode();
-			nodes[i].translate(1000 * r.nextFloat(), 1000 * r.nextFloat());
-			nodes[i].scale(1000 * r.nextFloat());	
-			bounds[i] = new PBounds(1000 * r.nextFloat(), 1000 * r.nextFloat(), 100, 80);
-		}
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            parent.addChild(nodes[i]);
+        }
+        log.endTest("Add " + NUMBER_NODES + " nodes to a new parent");
+    }
 
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			bounds[i].intersects(pickRect);
-		}		
-		log.endTest("Do intersects test for " + NUMBER_NODES + " bounds");
-			
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			nodes[i].localToParent(bounds[i]);
-		}		
-		log.endTest("Transform " + NUMBER_NODES + " bounds from local to parent");
-		
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			pickRect.add(bounds[i]);
-		}		
-		log.endTest("Sum " + NUMBER_NODES + " bounds");
-		
-		PBounds b = new PBounds(r.nextDouble(), r.nextDouble(), r.nextDouble(), r.nextDouble());
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES * 10; i++) {
-			b.clone();
-		}				
-		log.endTest("Clone " + NUMBER_NODES * 10 + " PBounds");
+    public void removeNodes() {
+        PNode parent = new PNode();
+        PNode[] nodes = new PNode[NUMBER_NODES];
+        ArrayList list = new ArrayList();
 
-	}
-	
-	public void renderSpeed() {
-		Random r = new Random();
-		PAffineTransform at = new PAffineTransform();
-		at.setScale(r.nextFloat());
-		at.translate(r.nextFloat(), r.nextFloat());
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i] = new PNode();
+        }
 
-		try {
-			log.startTest();
-			for (int i = 0; i < NUMBER_NODES; i++) {
-				at.createInverse();
-			}		
-			log.endTest("Create inverse transform " + NUMBER_NODES + " times");
-		} catch (NoninvertibleTransformException e) {
-		}
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            parent.addChild(nodes[i]);
+            list.add(nodes[i]);
+        }
 
-		int height = 400;
-		int width = 400;
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            parent.removeChild(nodes[i]);
+        }
+        log.endTest("Remove " + NUMBER_NODES + " nodes using removeChild() front to back");
 
-		double scale1 = 0.5;
-		double scale2 = 2;
-		boolean scaleFlip = true;
+        parent.addChildren(list);
 
-		PAffineTransform transorm1 = new PAffineTransform();
-		//transorm1.scale(0.5, 0.5);
-		transorm1.translate(0.5, 10.1);
-		PAffineTransform transorm2 = null;
-		
-		try {
-			transorm2 = new PAffineTransform(transorm1.createInverse());
-		} catch (NoninvertibleTransformException e) {}
-			
-			
-		GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
-		BufferedImage result = (BufferedImage)graphicsConfiguration.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
-		Graphics2D g2 = result.createGraphics();
+        log.startTest();
+        for (int i = NUMBER_NODES - 1; i >= 0; i--) {
+            parent.removeChild(i);
+        }
+        log.endTest("Remove " + NUMBER_NODES + " nodes using removeChild() back to front by index");
 
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			if (scaleFlip) {
-				g2.scale(scale2, scale2);
-				scaleFlip = !scaleFlip;
-			} else {
-				g2.scale(scale1, scale1);
-				scaleFlip = !scaleFlip;
-			}
-		}		
-		log.endTest("Scale graphics context " + NUMBER_NODES + " times");
+        log.startTest();
+        // for (int i = NUMBER_NODES - 1; i >= 0; i--) {
+        // parent.removeChild(nodes[i]);
+        // }
+        log.endTest("Remove " + NUMBER_NODES + " nodes using removeChild() back to front by object, TO_SLOW");
 
-		g2.setTransform(new AffineTransform());
-		
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			g2.translate(0.5, 0.5);
-		}		
-		log.endTest("Translate graphics context " + NUMBER_NODES + " times");
+        parent.addChildren(list);
 
-		g2.setTransform(new AffineTransform());
+        log.startTest();
+        parent.removeChildren(list);
+        log.endTest("Remove " + NUMBER_NODES + " nodes using removeChildren()");
 
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			if (scaleFlip) {
-				g2.transform(transorm1);
-				scaleFlip = !scaleFlip;
-			} else {
-				g2.transform(transorm2);
-				scaleFlip = !scaleFlip;
-			}
-		}		
-		log.endTest("Transform graphics context " + NUMBER_NODES + " times");		
+        parent.addChildren(list);
 
-	
-		Rectangle2D rect = new Rectangle2D.Double(0, 0, 100, 80);
-		GeneralPath path = new GeneralPath(rect);
-		
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			g2.fill(rect);
-		}		
-		log.endTest("Fill " + NUMBER_NODES + " rects"); 	
+        log.startTest();
+        parent.removeAllChildren();
+        log.endTest("Remove " + NUMBER_NODES + " nodes using removeAllChildren()");
+    }
 
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			g2.getTransform().getScaleX();
-		}		
-		log.endTest("Call g2.getTransform() " + NUMBER_NODES + " times");		
+    public void translateNodes() {
+        PNode parent = new PNode();
+        PNode[] nodes = new PNode[NUMBER_NODES];
+        PBounds b = new PBounds();
+        Random r = new Random();
 
-		
-		log.startTest();
-		for (int i = 0; i < NUMBER_NODES; i++) {
-			g2.fill(path);
-		}		
-		log.endTest("Fill " + NUMBER_NODES + " paths"); 	
-	}	
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i] = new PNode();
+            nodes[i].setBounds(1000 * r.nextFloat(), 1000 * r.nextFloat(), 100, 80);
+            parent.addChild(nodes[i]);
+            nodes[i].getFullBoundsReference();
+        }
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i].translate(1000 * r.nextFloat(), 1000 * r.nextFloat());
+            nodes[i].scale(1000 * r.nextFloat());
+            // nodes[i].translateBy(100.01, 100.2);
+            // nodes[i].scaleBy(0.9);
+        }
+        log.endTest("Translate " + NUMBER_NODES + " nodes, not counting repaint or validate layout");
+
+        log.startTest();
+        // parent.validateFullBounds(); now protected.
+        parent.getFullBoundsReference(); // calls validateFullBounds as a side
+                                         // effect.
+        log.endTest("Validate Layout after translate " + NUMBER_NODES + " nodes");
+
+        log.startTest();
+        parent.validateFullPaint();
+        log.endTest("Validate Paint after translate " + NUMBER_NODES + " nodes");
+
+        log.startTest();
+        parent.computeFullBounds(b);
+        log.endTest("Parent compute bounds of " + NUMBER_NODES + " children nodes");
+    }
+
+    public void fullIntersectsNodes() {
+        PNode parent = new PNode();
+        PNode[] nodes = new PNode[NUMBER_NODES];
+        PBounds b = new PBounds(0, 50, 100, 20);
+
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i] = new PNode();
+            parent.addChild(nodes[i]);
+        }
+
+        // parent.validateFullBounds(); // now protected
+        parent.getFullBoundsReference(); // calls validateFullBounds as a side
+                                         // effect.
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i].fullIntersects(b);
+        }
+        log.endTest("Do fullIntersects test for " + NUMBER_NODES + " nodes");
+    }
+
+    public void memorySizeOfNodes() {
+        PNode[] nodes = new PNode[NUMBER_NODES];
+        Runtime.getRuntime().gc();
+        long startTotalMemory = Runtime.getRuntime().totalMemory();
+        long startFree = Runtime.getRuntime().freeMemory();
+        long endFree;
+        long endTotal;
+
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i] = new PNode();
+        }
+
+        Runtime.getRuntime().gc();
+        endFree = Runtime.getRuntime().freeMemory();
+        endTotal = Runtime.getRuntime().totalMemory();
+
+        log.addEntry("Approximate k used by " + NUMBER_NODES + " nodes",
+                ((endTotal - startTotalMemory) + (startFree - endFree)) / 1024);
+        nodes[0].getPaint();
+    }
+
+    public void copyNodes() {
+        PNode parent = new PNode();
+        PNode[] nodes = new PNode[NUMBER_NODES];
+
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i] = new PNode();
+            parent.addChild(nodes[i]);
+        }
+
+        log.startTest();
+        parent.clone();
+        log.endTest("Copy/Serialize " + NUMBER_NODES + " nodes");
+    }
+
+    public void costOfNoBoundsCache() {
+        PNode[] nodes = new PNode[NUMBER_NODES];
+        PBounds[] bounds = new PBounds[NUMBER_NODES];
+        PBounds pickRect = new PBounds(0, 0, 1, 1);
+        Random r = new Random();
+
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i] = new PNode();
+            nodes[i].translate(1000 * r.nextFloat(), 1000 * r.nextFloat());
+            nodes[i].scale(1000 * r.nextFloat());
+            bounds[i] = new PBounds(1000 * r.nextFloat(), 1000 * r.nextFloat(), 100, 80);
+        }
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            bounds[i].intersects(pickRect);
+        }
+        log.endTest("Do intersects test for " + NUMBER_NODES + " bounds");
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            nodes[i].localToParent(bounds[i]);
+        }
+        log.endTest("Transform " + NUMBER_NODES + " bounds from local to parent");
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            pickRect.add(bounds[i]);
+        }
+        log.endTest("Sum " + NUMBER_NODES + " bounds");
+
+        PBounds b = new PBounds(r.nextDouble(), r.nextDouble(), r.nextDouble(), r.nextDouble());
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES * 10; i++) {
+            b.clone();
+        }
+        log.endTest("Clone " + NUMBER_NODES * 10 + " PBounds");
+
+    }
+
+    public void renderSpeed() {
+        Random r = new Random();
+        PAffineTransform at = new PAffineTransform();
+        at.setScale(r.nextFloat());
+        at.translate(r.nextFloat(), r.nextFloat());
+
+        try {
+            log.startTest();
+            for (int i = 0; i < NUMBER_NODES; i++) {
+                at.createInverse();
+            }
+            log.endTest("Create inverse transform " + NUMBER_NODES + " times");
+        }
+        catch (NoninvertibleTransformException e) {
+        }
+
+        int height = 400;
+        int width = 400;
+
+        double scale1 = 0.5;
+        double scale2 = 2;
+        boolean scaleFlip = true;
+
+        PAffineTransform transorm1 = new PAffineTransform();
+        // transorm1.scale(0.5, 0.5);
+        transorm1.translate(0.5, 10.1);
+        PAffineTransform transorm2 = null;
+
+        try {
+            transorm2 = new PAffineTransform(transorm1.createInverse());
+        }
+        catch (NoninvertibleTransformException e) {
+        }
+
+        GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment()
+                .getDefaultScreenDevice().getDefaultConfiguration();
+        BufferedImage result = (BufferedImage) graphicsConfiguration.createCompatibleImage(width, height,
+                Transparency.TRANSLUCENT);
+        Graphics2D g2 = result.createGraphics();
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            if (scaleFlip) {
+                g2.scale(scale2, scale2);
+                scaleFlip = !scaleFlip;
+            }
+            else {
+                g2.scale(scale1, scale1);
+                scaleFlip = !scaleFlip;
+            }
+        }
+        log.endTest("Scale graphics context " + NUMBER_NODES + " times");
+
+        g2.setTransform(new AffineTransform());
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            g2.translate(0.5, 0.5);
+        }
+        log.endTest("Translate graphics context " + NUMBER_NODES + " times");
+
+        g2.setTransform(new AffineTransform());
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            if (scaleFlip) {
+                g2.transform(transorm1);
+                scaleFlip = !scaleFlip;
+            }
+            else {
+                g2.transform(transorm2);
+                scaleFlip = !scaleFlip;
+            }
+        }
+        log.endTest("Transform graphics context " + NUMBER_NODES + " times");
+
+        Rectangle2D rect = new Rectangle2D.Double(0, 0, 100, 80);
+        GeneralPath path = new GeneralPath(rect);
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            g2.fill(rect);
+        }
+        log.endTest("Fill " + NUMBER_NODES + " rects");
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            g2.getTransform().getScaleX();
+        }
+        log.endTest("Call g2.getTransform() " + NUMBER_NODES + " times");
+
+        log.startTest();
+        for (int i = 0; i < NUMBER_NODES; i++) {
+            g2.fill(path);
+        }
+        log.endTest("Fill " + NUMBER_NODES + " paths");
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/SerializationTest.java b/core/src/test/java/edu/umd/cs/piccolo/SerializationTest.java
index 868e500..1c4c921 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/SerializationTest.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/SerializationTest.java
@@ -1,4 +1,5 @@
 package edu.umd.cs.piccolo;
+
 import java.util.Iterator;
 
 import junit.framework.TestCase;
@@ -7,27 +8,26 @@
 
 public class SerializationTest extends TestCase {
 
-	public SerializationTest(String name) {
-		super(name);
-	}
-	
-	public void test() {
-		PNode l = new PLayer();
-		
-		
-		for (int i = 0; i < 100; i++) {
-			l.addChild(new PNode());
-			l.addChild(new PText("Hello World"));
-			l.addChild(new PPath());
-		}
-		
-		l = (PNode) l.clone(); // copy uses serialization internally
-		assertTrue(l.getChildrenCount() == 300);
-		
-		Iterator i = l.getChildrenIterator();
-		while (i.hasNext()) {
-			PNode each = (PNode) i.next();
-			assertEquals(l, each.getParent());
-		}
-	}	
+    public SerializationTest(String name) {
+        super(name);
+    }
+
+    public void test() {
+        PNode l = new PLayer();
+
+        for (int i = 0; i < 100; i++) {
+            l.addChild(new PNode());
+            l.addChild(new PText("Hello World"));
+            l.addChild(new PPath());
+        }
+
+        l = (PNode) l.clone(); // copy uses serialization internally
+        assertTrue(l.getChildrenCount() == 300);
+
+        Iterator i = l.getChildrenIterator();
+        while (i.hasNext()) {
+            PNode each = (PNode) i.next();
+            assertEquals(l, each.getParent());
+        }
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/activities/PTransformActivityTest.java b/core/src/test/java/edu/umd/cs/piccolo/activities/PTransformActivityTest.java
index c38b829..d5f1340 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/activities/PTransformActivityTest.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/activities/PTransformActivityTest.java
@@ -1,16 +1,17 @@
 package edu.umd.cs.piccolo.activities;
+
 import edu.umd.cs.piccolo.activities.PTransformActivity;
 
 import junit.framework.TestCase;
 
 public class PTransformActivityTest extends TestCase {
 
-	public PTransformActivityTest(String name) {
-		super(name);
-	}
-	
-	public void testToString() {
-		PTransformActivity transformActivity = new PTransformActivity(1000, 0, null);
-		assertNotNull(transformActivity.toString());
-	}
+    public PTransformActivityTest(String name) {
+        super(name);
+    }
+
+    public void testToString() {
+        PTransformActivity transformActivity = new PTransformActivity(1000, 0, null);
+        assertNotNull(transformActivity.toString());
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/event/PZoomEventHandlerTest.java b/core/src/test/java/edu/umd/cs/piccolo/event/PZoomEventHandlerTest.java
index f2d4078..7cbcb8e 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/event/PZoomEventHandlerTest.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/event/PZoomEventHandlerTest.java
@@ -1,16 +1,17 @@
 package edu.umd.cs.piccolo.event;
+
 import edu.umd.cs.piccolo.event.PZoomEventHandler;
 
 import junit.framework.TestCase;
 
 public class PZoomEventHandlerTest extends TestCase {
 
-	public PZoomEventHandlerTest(String name) {
-		super(name);
-	}
-	
-	public void testToString() {
-		PZoomEventHandler zoomEventHandler = new PZoomEventHandler();
-		assertNotNull(zoomEventHandler.toString());
-	}
+    public PZoomEventHandlerTest(String name) {
+        super(name);
+    }
+
+    public void testToString() {
+        PZoomEventHandler zoomEventHandler = new PZoomEventHandler();
+        assertNotNull(zoomEventHandler.toString());
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/nodes/PImageTest.java b/core/src/test/java/edu/umd/cs/piccolo/nodes/PImageTest.java
index 2d14cd8..e7a4117 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/nodes/PImageTest.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/nodes/PImageTest.java
@@ -1,4 +1,5 @@
 package edu.umd.cs.piccolo.nodes;
+
 import java.awt.image.BufferedImage;
 
 import junit.framework.TestCase;
@@ -8,20 +9,20 @@
 
 public class PImageTest extends TestCase {
 
-	public PImageTest(String name) {
-		super(name);
-	}
-	
-	public void testCopy() {
-		PImage aNode = new PImage(new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB));
-		aNode = (PImage) aNode.clone();
-		assertNotNull(aNode.getImage());
-		assertEquals(aNode.getBounds(), new PBounds(0, 0, 100, 100));
-	}	
-	
-	public void testToString() {
-		PImage aNode = new PImage(new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB));
-		aNode.getFullBoundsReference();
-		assertNotNull(aNode.toString());
-	}
+    public PImageTest(String name) {
+        super(name);
+    }
+
+    public void testCopy() {
+        PImage aNode = new PImage(new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB));
+        aNode = (PImage) aNode.clone();
+        assertNotNull(aNode.getImage());
+        assertEquals(aNode.getBounds(), new PBounds(0, 0, 100, 100));
+    }
+
+    public void testToString() {
+        PImage aNode = new PImage(new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB));
+        aNode.getFullBoundsReference();
+        assertNotNull(aNode.toString());
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/nodes/PPathTest.java b/core/src/test/java/edu/umd/cs/piccolo/nodes/PPathTest.java
index d25666d..3d96b2a 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/nodes/PPathTest.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/nodes/PPathTest.java
@@ -1,4 +1,5 @@
 package edu.umd.cs.piccolo.nodes;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -13,39 +14,42 @@
 
 public class PPathTest extends TestCase {
 
-	public PPathTest(String name) {
-		super(name);
-	}
-	
-	public void testCopy() {
-		PPath p = PPath.createEllipse(0, 0, 100, 100);
-		PBounds b = p.getBounds();
-		p = (PPath) p.clone();
-		assertEquals(p.getBounds(), b);
-	}	
-	
-	public void testSaveToFile() {
-		PPath p = PPath.createEllipse(0, 0, 100, 100);
-		PBounds b = p.getBounds();
-		try {
-			File file = new File("myfile");
-			FileOutputStream fout = new FileOutputStream(file);
-			PObjectOutputStream out = new PObjectOutputStream(fout);
-			out.writeObjectTree(p);
-			out.flush();
-			out.close();
-			
-			FileInputStream fin = new FileInputStream(file);
-			ObjectInputStream in = new ObjectInputStream(fin);
-			p = (PPath) in.readObject();
-			assertEquals(p.getBounds(), b);
-			file.delete();
-		} catch (FileNotFoundException e) {
-			assertTrue(false);
-		} catch (ClassNotFoundException e) {
-			assertTrue(false);
-		} catch (IOException e) {
-			assertTrue(false);
-		}
-	}
+    public PPathTest(String name) {
+        super(name);
+    }
+
+    public void testCopy() {
+        PPath p = PPath.createEllipse(0, 0, 100, 100);
+        PBounds b = p.getBounds();
+        p = (PPath) p.clone();
+        assertEquals(p.getBounds(), b);
+    }
+
+    public void testSaveToFile() {
+        PPath p = PPath.createEllipse(0, 0, 100, 100);
+        PBounds b = p.getBounds();
+        try {
+            File file = new File("myfile");
+            FileOutputStream fout = new FileOutputStream(file);
+            PObjectOutputStream out = new PObjectOutputStream(fout);
+            out.writeObjectTree(p);
+            out.flush();
+            out.close();
+
+            FileInputStream fin = new FileInputStream(file);
+            ObjectInputStream in = new ObjectInputStream(fin);
+            p = (PPath) in.readObject();
+            assertEquals(p.getBounds(), b);
+            file.delete();
+        }
+        catch (FileNotFoundException e) {
+            assertTrue(false);
+        }
+        catch (ClassNotFoundException e) {
+            assertTrue(false);
+        }
+        catch (IOException e) {
+            assertTrue(false);
+        }
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/nodes/PTextTest.java b/core/src/test/java/edu/umd/cs/piccolo/nodes/PTextTest.java
index 5e908e2..cf04979 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/nodes/PTextTest.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/nodes/PTextTest.java
@@ -1,41 +1,42 @@
 package edu.umd.cs.piccolo.nodes;
+
 import junit.framework.TestCase;
 
 import edu.umd.cs.piccolo.nodes.PText;
 
 public class PTextTest extends TestCase {
 
-	public PTextTest(String name) {
-		super(name);
-	}
-	
-	public void testCopy() {
-		PText aNode = new PText("Boo");
-		aNode = (PText) aNode.clone();
-		assertNotNull(aNode.getText());
-		assertNotNull(aNode.getFont());
-	}	
-	
-	public void testEmptyString() {
-		PText t = new PText();
-		t.setText("hello world");
-		t.setText("");
-		t.setText(null);
-	}
+    public PTextTest(String name) {
+        super(name);
+    }
 
-	public void testBoundsOfEmptyString() {
-		PText t = new PText();
-		t.setText("hello world");
-		assertTrue(t.getBoundsReference().getWidth() > 0);
-		t.setText("");
-		assertTrue(t.getBoundsReference().getWidth() == 0);
-		t.setText(null);
-		assertTrue(t.getBoundsReference().getWidth() == 0);
-	}
-	
-	public void testToString() {
-		PText t = new PText();
-		t.setText("hello world");
-		assertNotNull(t.toString());
-	}
+    public void testCopy() {
+        PText aNode = new PText("Boo");
+        aNode = (PText) aNode.clone();
+        assertNotNull(aNode.getText());
+        assertNotNull(aNode.getFont());
+    }
+
+    public void testEmptyString() {
+        PText t = new PText();
+        t.setText("hello world");
+        t.setText("");
+        t.setText(null);
+    }
+
+    public void testBoundsOfEmptyString() {
+        PText t = new PText();
+        t.setText("hello world");
+        assertTrue(t.getBoundsReference().getWidth() > 0);
+        t.setText("");
+        assertTrue(t.getBoundsReference().getWidth() == 0);
+        t.setText(null);
+        assertTrue(t.getBoundsReference().getWidth() == 0);
+    }
+
+    public void testToString() {
+        PText t = new PText();
+        t.setText("hello world");
+        assertNotNull(t.toString());
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/util/PAffineTransformTest.java b/core/src/test/java/edu/umd/cs/piccolo/util/PAffineTransformTest.java
index 95cd3c0..356f78c 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/util/PAffineTransformTest.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/util/PAffineTransformTest.java
@@ -1,68 +1,69 @@
 package edu.umd.cs.piccolo.util;
+
 import junit.framework.TestCase;
 
 import edu.umd.cs.piccolo.util.PAffineTransform;
 import edu.umd.cs.piccolo.util.PBounds;
 
 public class PAffineTransformTest extends TestCase {
-	
-	public PAffineTransformTest(String aName) {
-		super(aName);
-	}
-	
-	public void testRotation() {
-		PAffineTransform at = new PAffineTransform();
-		at.rotate(Math.toRadians(45));
-		assertEquals(at.getRotation(), Math.toRadians(45), 0.000000001);
-		at.setRotation(Math.toRadians(90));
-		assertEquals(at.getRotation(), Math.toRadians(90), 0.000000001);
-	}
 
-	public void testScale() {
-		PAffineTransform at = new PAffineTransform();
-		at.scaleAboutPoint(0.45, 0, 1);
-		assertEquals(at.getScale(), 0.45, 0.000000001);
-		at.setScale(0.11);
-		assertEquals(at.getScale(), 0.11, 0.000000001);
-	}
-	
-	public void testTransformRect() {
-		PBounds b1 = new PBounds(0, 0, 100, 80);
-		PBounds b2 = new PBounds(100, 100, 100, 80);
-		
-		PAffineTransform at = new PAffineTransform();
-		at.scale(0.5, 0.5);
-		at.translate(100, 50);
-				
-		at.transform(b1, b1);
-		at.transform(b2, b2);
+    public PAffineTransformTest(String aName) {
+        super(aName);
+    }
 
-		PBounds b3 = new PBounds();
-		PBounds b4 = new PBounds(0, 0, 100, 100);
-		
-		assertTrue(at.transform(b3, b4).isEmpty());
-		
-		assertEquals(b1.getX(), 50, 0.000000001);
-		assertEquals(b1.getY(), 25, 0.000000001);
-		assertEquals(b1.getWidth(), 50, 0.000000001);
-		assertEquals(b1.getHeight(), 40, 0.000000001);
+    public void testRotation() {
+        PAffineTransform at = new PAffineTransform();
+        at.rotate(Math.toRadians(45));
+        assertEquals(at.getRotation(), Math.toRadians(45), 0.000000001);
+        at.setRotation(Math.toRadians(90));
+        assertEquals(at.getRotation(), Math.toRadians(90), 0.000000001);
+    }
 
-		assertEquals(b2.getX(), 100, 0.000000001);
-		assertEquals(b2.getY(), 75, 0.000000001);
-		assertEquals(b2.getWidth(), 50, 0.000000001);
-		assertEquals(b2.getHeight(), 40, 0.000000001);
+    public void testScale() {
+        PAffineTransform at = new PAffineTransform();
+        at.scaleAboutPoint(0.45, 0, 1);
+        assertEquals(at.getScale(), 0.45, 0.000000001);
+        at.setScale(0.11);
+        assertEquals(at.getScale(), 0.11, 0.000000001);
+    }
 
-		at.inverseTransform(b1, b1);
-		at.inverseTransform(b2, b2);
+    public void testTransformRect() {
+        PBounds b1 = new PBounds(0, 0, 100, 80);
+        PBounds b2 = new PBounds(100, 100, 100, 80);
 
-		assertEquals(b1.getX(), 0, 0.000000001);
-		assertEquals(b1.getY(), 0, 0.000000001);
-		assertEquals(b1.getWidth(), 100, 0.000000001);
-		assertEquals(b1.getHeight(), 80, 0.000000001);
+        PAffineTransform at = new PAffineTransform();
+        at.scale(0.5, 0.5);
+        at.translate(100, 50);
 
-		assertEquals(b2.getX(), 100, 0.000000001);
-		assertEquals(b2.getY(), 100, 0.000000001);
-		assertEquals(b2.getWidth(), 100, 0.000000001);
-		assertEquals(b2.getHeight(), 80, 0.000000001);				
-	}	
+        at.transform(b1, b1);
+        at.transform(b2, b2);
+
+        PBounds b3 = new PBounds();
+        PBounds b4 = new PBounds(0, 0, 100, 100);
+
+        assertTrue(at.transform(b3, b4).isEmpty());
+
+        assertEquals(b1.getX(), 50, 0.000000001);
+        assertEquals(b1.getY(), 25, 0.000000001);
+        assertEquals(b1.getWidth(), 50, 0.000000001);
+        assertEquals(b1.getHeight(), 40, 0.000000001);
+
+        assertEquals(b2.getX(), 100, 0.000000001);
+        assertEquals(b2.getY(), 75, 0.000000001);
+        assertEquals(b2.getWidth(), 50, 0.000000001);
+        assertEquals(b2.getHeight(), 40, 0.000000001);
+
+        at.inverseTransform(b1, b1);
+        at.inverseTransform(b2, b2);
+
+        assertEquals(b1.getX(), 0, 0.000000001);
+        assertEquals(b1.getY(), 0, 0.000000001);
+        assertEquals(b1.getWidth(), 100, 0.000000001);
+        assertEquals(b1.getHeight(), 80, 0.000000001);
+
+        assertEquals(b2.getX(), 100, 0.000000001);
+        assertEquals(b2.getY(), 100, 0.000000001);
+        assertEquals(b2.getWidth(), 100, 0.000000001);
+        assertEquals(b2.getHeight(), 80, 0.000000001);
+    }
 }
diff --git a/core/src/test/java/edu/umd/cs/piccolo/util/PPickPathTest.java b/core/src/test/java/edu/umd/cs/piccolo/util/PPickPathTest.java
index e59c9a6..1cdff23 100644
--- a/core/src/test/java/edu/umd/cs/piccolo/util/PPickPathTest.java
+++ b/core/src/test/java/edu/umd/cs/piccolo/util/PPickPathTest.java
@@ -1,4 +1,5 @@
 package edu.umd.cs.piccolo.util;
+
 import edu.umd.cs.piccolo.PCamera;
 import edu.umd.cs.piccolo.PCanvas;
 import edu.umd.cs.piccolo.PLayer;
@@ -9,32 +10,32 @@
 
 public class PPickPathTest extends TestCase {
 
-	public PPickPathTest(String name) {
-		super(name);
-	}
+    public PPickPathTest(String name) {
+        super(name);
+    }
 
-	public void testPick() {
-		PCanvas canvas = new PCanvas();
-		PCamera camera = canvas.getCamera();
-		PLayer layer = canvas.getLayer();
-		
-		camera.setBounds(0, 0, 100, 100);
-		
-		PNode a = PPath.createRectangle(0, 0, 100, 100);
-		PNode b = PPath.createRectangle(0, 0, 100, 100);
-		PNode c = PPath.createRectangle(0, 0, 100, 100);
-		
-		layer.addChild(a);
-		layer.addChild(b);
-		layer.addChild(c);
-		
-		PPickPath pickPath = camera.pick(50, 50, 2);
-		
-		assertTrue(pickPath.getPickedNode() == c);
-		assertTrue(pickPath.nextPickedNode() == b);
-		assertTrue(pickPath.nextPickedNode() == a);
-		assertTrue(pickPath.nextPickedNode() == camera);
-		assertTrue(pickPath.nextPickedNode() == null);
-		assertTrue(pickPath.nextPickedNode() == null);
-	}
+    public void testPick() {
+        PCanvas canvas = new PCanvas();
+        PCamera camera = canvas.getCamera();
+        PLayer layer = canvas.getLayer();
+
+        camera.setBounds(0, 0, 100, 100);
+
+        PNode a = PPath.createRectangle(0, 0, 100, 100);
+        PNode b = PPath.createRectangle(0, 0, 100, 100);
+        PNode c = PPath.createRectangle(0, 0, 100, 100);
+
+        layer.addChild(a);
+        layer.addChild(b);
+        layer.addChild(c);
+
+        PPickPath pickPath = camera.pick(50, 50, 2);
+
+        assertTrue(pickPath.getPickedNode() == c);
+        assertTrue(pickPath.nextPickedNode() == b);
+        assertTrue(pickPath.nextPickedNode() == a);
+        assertTrue(pickPath.nextPickedNode() == camera);
+        assertTrue(pickPath.nextPickedNode() == null);
+        assertTrue(pickPath.nextPickedNode() == null);
+    }
 }
get/add attribute methods provide access to
-	 * a small per-instance attribute set. Callers can use get/add attribute
-	 * to annotate nodes that were created by another module.
-	 * get/add attribute methods provide access to
+     * a small per-instance attribute set. Callers can use get/add attribute
+     * to annotate nodes that were created by another module.
+     * setBounds. 
-	 * When you are done call endResizeBounds to let the node know that
-	 * you are done.
-	 */
-	public void startResizeBounds() {
-	}
-				
-	/**
-	 * Notify this node that you have finished a resize bounds sequence.
-	 */
-	public void endResizeBounds() {
-	}	
+    // ****************************************************************
+    // Bounds Geometry - Methods for setting and querying the bounds
+    // of this node.
+    //
+    // The bounds of a node store the node's position and size in
+    // the nodes local coordinate system. Many node subclasses will need
+    // to override the setBounds method so that they can update their
+    // internal state appropriately. See PPath for an example.
+    //
+    // Since the bounds are stored in the local coordinate system
+    // they WILL NOT change if the node is scaled, translated, or rotated.
+    //
+    // The bounds may be accessed with either getBounds, or
+    // getBoundsReference. The former returns a copy of the bounds
+    // the latter returns a reference to the nodes bounds that should
+    // normally not be modified. If a node is marked as volatile then
+    // it may modify its bounds before returning them from getBoundsReference,
+    // otherwise it may not.
+    // ****************************************************************
 
-	public boolean setX(double x) {
-		return setBounds(x, getY(), getWidth(), getHeight());		
-	}
-	
-	public boolean setY(double y) {
-		return setBounds(getX(), y, getWidth(), getHeight());
-	}
-	
-	public boolean setWidth(double width) {
-		return setBounds(getX(), getY(), width, getHeight());
-	}
-	
-	public boolean setHeight(double height) {
-		return setBounds(getX(), getY(), getWidth(), height);
-	}
-	
-	/**
-	 * Set the bounds of this node to the given value. These bounds 
-	 * are stored in the local coordinate system of this node.
-	 * 
-	 * @return true if the bounds changed.
-	 */
-	public boolean setBounds(Rectangle2D newBounds) {
-		return setBounds(newBounds.getX(), newBounds.getY(), newBounds.getWidth(), newBounds.getHeight());
-	}
+    /**
+     * Return a copy of this node's bounds. These bounds are stored in the local
+     * coordinate system of this node and do not include the bounds of any of
+     * this node's children.
+     */
+    public PBounds getBounds() {
+        return (PBounds) getBoundsReference().clone();
+    }
 
-	/**
-	 * Set the bounds of this node to the given value. These bounds 
-	 * are stored in the local coordinate system of this node.
-	 * 
-	 * If the width or height is less then or equal to zero then the bound's
-	 * emtpy bit will be set to true.
-	 * 
-	 * Subclasses must call the super.setBounds() method.
-	 *	
-	 * @return true if the bounds changed.
-	 */
-	public boolean setBounds(double x, double y, double width, double height) {
-		if (bounds.x != x || bounds.y != y || bounds.width != width || bounds.height != height) {
-			bounds.setRect(x, y, width, height);
+    /**
+     * Return a direct reference to this node's bounds. These bounds are stored
+     * in the local coordinate system of this node and do not include the bounds
+     * of any of this node's children. The value returned should not be
+     * modified.
+     */
+    public PBounds getBoundsReference() {
+        return bounds;
+    }
 
-			if (width <= 0 || height <= 0) {
-				bounds.reset();
-			}
+    /**
+     * Notify this node that you will beging to repeadily call setBounds
+     * . When you are done call endResizeBounds to let the
+     * node know that you are done.
+     */
+    public void startResizeBounds() {
+    }
 
-			internalUpdateBounds(x, y, width, height);
-			invalidatePaint();
-			signalBoundsChanged();			
-			return true;
-		}
-		// Don't put any invalidating code here or else nodes with volatile bounds will
-		// create a soft infinite loop (calling Swing.invokeLater()) when they validate
-		// their bounds.
-		return false;		
-	}
+    /**
+     * Notify this node that you have finished a resize bounds sequence.
+     */
+    public void endResizeBounds() {
+    }
 
-	/**
-	 * Gives nodes a chance to update their internal structure
-	 * before bounds changed notifications are sent. When this message
-	 * is recived the nodes bounds field will contain the new value.
-	 * 
-	 * See PPath for an example that uses this method.
-	 */
-	protected void internalUpdateBounds(double x, double y, double width, double height) {
-	}
-	
-	/**
-	 * Set the empty bit of this bounds to true.
-	 */
-	public void resetBounds() {
-		setBounds(0, 0, 0, 0);
-	}
+    public boolean setX(double x) {
+        return setBounds(x, getY(), getWidth(), getHeight());
+    }
 
-	/**
-	 * Return the x position (in local coords) of this node's bounds.
-	 */
-	public double getX() {
-		return getBoundsReference().getX();
-	}
+    public boolean setY(double y) {
+        return setBounds(getX(), y, getWidth(), getHeight());
+    }
 
-	/**
-	 * Return the y position (in local coords) of this node's bounds.
-	 */
-	public double getY() {
-		return getBoundsReference().getY();
-	}
-		
-	/**
-	 * Return the width (in local coords) of this node's bounds.
-	 */
-	public double getWidth() {
-		return getBoundsReference().getWidth();
-	}
+    public boolean setWidth(double width) {
+        return setBounds(getX(), getY(), width, getHeight());
+    }
 
-	/**
-	 * Return the height (in local coords) of this node's bounds.
-	 */
-	public double getHeight() {
-		return getBoundsReference().getHeight();
-	}
-	
-	/**
-	 * Return a copy of the bounds of this node in the global 
-	 * coordinate system.
-	 * 
-	 * @return the bounds in global coordinate system.
-	 */
-	public PBounds getGlobalBounds() {
-		return (PBounds) localToGlobal(getBounds());
-	}
-	
-	/**
-	 * Center the bounds of this node so that they are centered on the given
-	 * point specified on the local coords of this node. Note that this meathod
-	 * will modify the nodes bounds, while centerFullBoundsOnPoint will modify
-	 * the nodes transform.
-	 * 
-	 * @return true if the bounds changed.
-	 */
-	public boolean centerBoundsOnPoint(double localX, double localY) {
-		double dx = localX - bounds.getCenterX();
-		double dy = localY - bounds.getCenterY();
-		return setBounds(bounds.x + dx, bounds.y + dy, bounds.width, bounds.height);
-	}
+    public boolean setHeight(double height) {
+        return setBounds(getX(), getY(), getWidth(), height);
+    }
 
-	/**
-	 * Center the ffull bounds of this node so that they are centered on the
-	 * given point specified on the local coords of this nodes parent. Note that
-	 * this meathod will modify the nodes transform, while centerBoundsOnPoint
-	 * will modify the nodes bounds.
-	 */
-	public void centerFullBoundsOnPoint(double parentX, double parentY) {
-		double dx = parentX - getFullBoundsReference().getCenterX();
-		double dy = parentY - getFullBoundsReference().getCenterY();
-		offset(dx, dy);
-	}
-	
-	/**
-	 * Return true if this node intersects the given rectangle specified in
-	 * local bounds. If the geometry of this node is complex this method can become
-	 * expensive, it is therefore recommended that fullIntersects is used
-	 * for quick rejects before calling this method.
-	 * 
-	 * @param localBounds the bounds to test for intersection against
-	 * @return true if the given rectangle intersects this nodes geometry.
-	 */
-	public boolean intersects(Rectangle2D localBounds) {
-		if (localBounds == null) return true;
-		return getBoundsReference().intersects(localBounds);
-	}
+    /**
+     * Set the bounds of this node to the given value. These bounds are stored
+     * in the local coordinate system of this node.
+     * 
+     * @return true if the bounds changed.
+     */
+    public boolean setBounds(Rectangle2D newBounds) {
+        return setBounds(newBounds.getX(), newBounds.getY(), newBounds.getWidth(), newBounds.getHeight());
+    }
 
-	//****************************************************************
-	// Full Bounds - Methods for computing and querying the 
-	// full bounds of this node.
-	// 
-	// The full bounds of a node store the nodes bounds 
-	// together with the union of the bounds of all the 
-	// node's descendents. The full bounds are stored in the parent
-	// coordinate system of this node, the full bounds DOES change 
-	// when you translate, scale, or rotate this node.
-	// 
-	// The full bounds may be accessed with either getFullBounds, or 
-	// getFullBoundsReference. The former returns a copy of the full bounds
-	// the latter returns a reference to the node's full bounds that should
-	// not be modified.
-	//****************************************************************
+    /**
+     * Set the bounds of this node to the given value. These bounds are stored
+     * in the local coordinate system of this node.
+     * 
+     * If the width or height is less then or equal to zero then the bound's
+     * emtpy bit will be set to true.
+     * 
+     * Subclasses must call the super.setBounds() method.
+     * 
+     * @return true if the bounds changed.
+     */
+    public boolean setBounds(double x, double y, double width, double height) {
+        if (bounds.x != x || bounds.y != y || bounds.width != width || bounds.height != height) {
+            bounds.setRect(x, y, width, height);
 
-	/**
-	 * Return a copy of this node's full bounds. These bounds are stored in 
-	 * the parent coordinate system of this node and they include the
-	 * union of this node's bounds and all the bounds of it's descendents.
-	 * 
-	 * @return a copy of this node's full bounds. 
-	 */
-	public PBounds getFullBounds() {
-		return (PBounds) getFullBoundsReference().clone();
-	}
+            if (width <= 0 || height <= 0) {
+                bounds.reset();
+            }
 
-	/**
-	 * Return a reference to this node's full bounds cache. These bounds are 
-	 * stored in the parent coordinate system of this node and they include the
-	 * union of this node's bounds and all the bounds of it's descendents. The bounds
-	 * returned by this method should not be modified.
-	 * 
-	 * @return a reference to this node's full bounds cache. 
-	 */
-	public PBounds getFullBoundsReference() {
-		validateFullBounds();
-		return fullBoundsCache;
-	}
+            internalUpdateBounds(x, y, width, height);
+            invalidatePaint();
+            signalBoundsChanged();
+            return true;
+        }
+        // Don't put any invalidating code here or else nodes with volatile
+        // bounds will
+        // create a soft infinite loop (calling Swing.invokeLater()) when they
+        // validate
+        // their bounds.
+        return false;
+    }
 
-	/**
-	 * Compute and return the full bounds of this node. If the dstBounds
-	 * parameter is not null then it will be used to return the results instead
-	 * of creating a new PBounds.
-	 * 
-	 * @param dstBounds if not null the new bounds will be stored here
-	 * @return the full bounds in the parent coordinate system of this node
-	 */
-	public PBounds computeFullBounds(PBounds dstBounds) {
-		PBounds result = getUnionOfChildrenBounds(dstBounds);
-		result.add(getBoundsReference());
-		localToParent(result);
-		return result;
-	}
-	
-	/**
-	 * Compute and return the union of the full bounds of all the 
-	 * children of this node. If the dstBounds parameter is not null 
-	 * then it will be used to return the results instead of creating 
-	 * a new PBounds.
-	 * 
-	 * @param dstBounds if not null the new bounds will be stored here
-	 */
-	public PBounds getUnionOfChildrenBounds(PBounds dstBounds) {
-		if (dstBounds == null) {
-			dstBounds = new PBounds();
-		} else {
-			dstBounds.resetToZero();
-		}
-		
-		int count = getChildrenCount();
-		for (int i = 0; i < count; i++) {
-			PNode each = (PNode) children.get(i);
-			dstBounds.add(each.getFullBoundsReference());
-		}
-		
-		return dstBounds;
-	}	
-	
-	/**
-	 * Return a copy of the full bounds of this node in the global 
-	 * coordinate system.
-	 * 
-	 * @return the full bounds in global coordinate system.
-	 */
-	public PBounds getGlobalFullBounds() {
-		PBounds b = getFullBounds();
-		if (parent != null) {
-			parent.localToGlobal(b);
-		}
-		return b;
-	}	
+    /**
+     * Gives nodes a chance to update their internal structure before bounds
+     * changed notifications are sent. When this message is recived the nodes
+     * bounds field will contain the new value.
+     * 
+     * See PPath for an example that uses this method.
+     */
+    protected void internalUpdateBounds(double x, double y, double width, double height) {
+    }
 
-	/**
-	 * Return true if the full bounds of this node intersects with the
-	 * specified bounds.
-	 * 
-	 * @param parentBounds the bounds to test for intersection against (specified in parent's coordinate system)
-	 * @return true if this nodes full bounds intersect the given bounds.
-	 */
-	public boolean fullIntersects(Rectangle2D parentBounds) {
-		if (parentBounds == null) return true;
-		return getFullBoundsReference().intersects(parentBounds);
-	}
-	
-	//****************************************************************
-	// Bounds Damage Management - Methods used to invalidate and validate
-	// the bounds of nodes.
-	//****************************************************************
+    /**
+     * Set the empty bit of this bounds to true.
+     */
+    public void resetBounds() {
+        setBounds(0, 0, 0, 0);
+    }
 
-	/**
-	 * Return true if this nodes bounds may change at any time. The default
-	 * behavior is to return false, subclasses that override this method to
-	 * return true should also override getBoundsReference() and compute their
-	 * volatile bounds there before returning the reference.
-	 * 
-	 * @return true if this node has volatile bounds
-	 */
-	protected boolean getBoundsVolatile() {
-		return false;
-	}
-		
-	/**
-	 * Return true if this node has a child with volatile bounds.
-	 * 
-	 * @return true if this node has a child with volatile bounds
-	 */
-	protected boolean getChildBoundsVolatile() {
-		return childBoundsVolatile;
-	}
+    /**
+     * Return the x position (in local coords) of this node's bounds.
+     */
+    public double getX() {
+        return getBoundsReference().getX();
+    }
 
-	/**
-	 * Set if this node has a child with volatile bounds. This should normally
-	 * be managed automatically by the bounds validation process.
-	 * 
-	 * @param childBoundsVolatile true if this node has a descendent with volatile bounds
-	 */
-	protected void setChildBoundsVolatile(boolean childBoundsVolatile) {
-		this.childBoundsVolatile = childBoundsVolatile;
-	}
+    /**
+     * Return the y position (in local coords) of this node's bounds.
+     */
+    public double getY() {
+        return getBoundsReference().getY();
+    }
 
-	/**
-	 * Return true if this node's bounds have recently changed. This flag
-	 * will be reset on the next call of validateFullBounds.
-	 * 
-	 * @return true if this node's bounds have changed.
-	 */ 	
-	protected boolean getBoundsChanged() {
-		return boundsChanged;
-	}
+    /**
+     * Return the width (in local coords) of this node's bounds.
+     */
+    public double getWidth() {
+        return getBoundsReference().getWidth();
+    }
 
-	/**
-	 * Set the bounds chnaged flag.  This flag
-	 * will be reset on the next call of validateFullBounds.
-	 * 
-	 * @param boundsChanged true if this nodes bounds have changed.
-	 */ 	
-	protected void setBoundsChanged(boolean boundsChanged) {
-		this.boundsChanged = boundsChanged;
-	}
-	
-	/**
-	 * Return true if the full bounds of this node are invalid. This means that
-	 * the full bounds of this node have changed and need to be recomputed.
-	 * 
-	 * @return true if the full bounds of this node are invalid
-	 */
-	protected boolean getFullBoundsInvalid() {
-		return fullBoundsInvalid;
-	}
+    /**
+     * Return the height (in local coords) of this node's bounds.
+     */
+    public double getHeight() {
+        return getBoundsReference().getHeight();
+    }
 
-	/**
-	 * Set the full bounds invalid flag. This flag is set when the full bounds of
-	 * this node need to be recomputed as is the case when this node is transformed
-	 * or when one of this node's children changes geometry.
-	 */
-	protected void setFullBoundsInvalid(boolean fullBoundsInvalid) {
-		this.fullBoundsInvalid = fullBoundsInvalid;
-	}
-	
-	/**
-	 * Return true if one of this node's descendents has invalid bounds.
-	 */
-	protected boolean getChildBoundsInvalid() {
-		return childBoundsInvalid;
-	}
+    /**
+     * Return a copy of the bounds of this node in the global coordinate system.
+     * 
+     * @return the bounds in global coordinate system.
+     */
+    public PBounds getGlobalBounds() {
+        return (PBounds) localToGlobal(getBounds());
+    }
 
-	/**
-	 * Set the flag indicating that one of this node's descendents has
-	 * invalid bounds.
-	 */
-	protected void setChildBoundsInvalid(boolean childBoundsInvalid) {		
-		this.childBoundsInvalid = childBoundsInvalid;
-	}
-	
-	/**
-	 * This method should be called when the bounds of this node are changed.
-	 * It invalidates the full bounds of this node, and also notifies each of 
-	 * this nodes children that their parent's bounds have changed. As a result 
-	 * of this method getting called this nodes layoutChildren will be called.
-	 */ 
-	public void signalBoundsChanged() {
-		invalidateFullBounds();
-		setBoundsChanged(true);
-		firePropertyChange(PROPERTY_CODE_BOUNDS, PROPERTY_BOUNDS, null, bounds); 	
+    /**
+     * Center the bounds of this node so that they are centered on the given
+     * point specified on the local coords of this node. Note that this meathod
+     * will modify the nodes bounds, while centerFullBoundsOnPoint will modify
+     * the nodes transform.
+     * 
+     * @return true if the bounds changed.
+     */
+    public boolean centerBoundsOnPoint(double localX, double localY) {
+        double dx = localX - bounds.getCenterX();
+        double dy = localY - bounds.getCenterY();
+        return setBounds(bounds.x + dx, bounds.y + dy, bounds.width, bounds.height);
+    }
 
-		int count = getChildrenCount();
-		for (int i = 0; i < count; i++) {
-			PNode each = (PNode) children.get(i);
-			each.parentBoundsChanged();
-		}
-	}
+    /**
+     * Center the ffull bounds of this node so that they are centered on the
+     * given point specified on the local coords of this nodes parent. Note that
+     * this meathod will modify the nodes transform, while centerBoundsOnPoint
+     * will modify the nodes bounds.
+     */
+    public void centerFullBoundsOnPoint(double parentX, double parentY) {
+        double dx = parentX - getFullBoundsReference().getCenterX();
+        double dy = parentY - getFullBoundsReference().getCenterY();
+        offset(dx, dy);
+    }
 
-	/**
-	 * Invalidate this node's layout, so that later 
-	 * layoutChildren will get called.
-	 */
-	public void invalidateLayout() {
-		invalidateFullBounds();
-	}
+    /**
+     * Return true if this node intersects the given rectangle specified in
+     * local bounds. If the geometry of this node is complex this method can
+     * become expensive, it is therefore recommended that
+     * fullIntersects is used for quick rejects before calling this
+     * method.
+     * 
+     * @param localBounds the bounds to test for intersection against
+     * @return true if the given rectangle intersects this nodes geometry.
+     */
+    public boolean intersects(Rectangle2D localBounds) {
+        if (localBounds == null)
+            return true;
+        return getBoundsReference().intersects(localBounds);
+    }
 
-	/**
-	 * A notification that the bounds of this node's parent have changed.
-	 */
-	protected void parentBoundsChanged() {
-	}
+    // ****************************************************************
+    // Full Bounds - Methods for computing and querying the
+    // full bounds of this node.
+    // 
+    // The full bounds of a node store the nodes bounds
+    // together with the union of the bounds of all the
+    // node's descendents. The full bounds are stored in the parent
+    // coordinate system of this node, the full bounds DOES change
+    // when you translate, scale, or rotate this node.
+    // 
+    // The full bounds may be accessed with either getFullBounds, or
+    // getFullBoundsReference. The former returns a copy of the full bounds
+    // the latter returns a reference to the node's full bounds that should
+    // not be modified.
+    // ****************************************************************
 
-	/**
-	 * Invalidates the full bounds of this node, and sets the child bounds invalid flag
-	 * on each of this node's ancestors.
-	 */ 
-	public void invalidateFullBounds() {
-		setFullBoundsInvalid(true);
-		
-		PNode n = parent;
-		while (n != null && !n.getChildBoundsInvalid()) {
-			n.setChildBoundsInvalid(true);
-			n = n.parent;
-		}
-		
-		if (SCENE_GRAPH_DELEGATE != null) SCENE_GRAPH_DELEGATE.nodeFullBoundsInvalidated(this);
-	}
-	
-	/**
-	 * This method is called to validate the bounds of this node and all of its 
-	 * descendents. It returns true if this nodes bounds or the bounds of any of its
-	 * descendents are marked as volatile.
-	 * 
-	 * @return true if this node or any of its descendents have volatile bounds
-	 */
-	protected boolean validateFullBounds() {
-		boolean boundsVolatile = getBoundsVolatile();
-		
-		// 1. Only compute new bounds if invalid flags are set.
-		if (fullBoundsInvalid || childBoundsInvalid || boundsVolatile || childBoundsVolatile) {
-			
-			// 2. If my bounds are volatile and they have not been changed then signal a change. 
-			// For most cases this will
-			// do nothing, but if a nodes bounds depend on its model, then validate bounds has the
-			// responsibility of making the bounds match the models value. For example PPaths
-			// validateBounds method makes sure that the bounds are equal to the bounds of the GeneralPath
-			// model.
-			if (boundsVolatile && !boundsChanged) {
-				signalBoundsChanged();
-			}
-			
-			// 3. If the bounds of on of my decendents are invalidate then validate the bounds of all
-			// of my children.
-			if (childBoundsInvalid || childBoundsVolatile) {
-				childBoundsVolatile = false;
-				int count = getChildrenCount();
-				for (int i = 0; i < count; i++) {
-					PNode each = (PNode) children.get(i);
-					childBoundsVolatile |= each.validateFullBounds();
-				}
-			}
+    /**
+     * Return a copy of this node's full bounds. These bounds are stored in the
+     * parent coordinate system of this node and they include the union of this
+     * node's bounds and all the bounds of it's descendents.
+     * 
+     * @return a copy of this node's full bounds.
+     */
+    public PBounds getFullBounds() {
+        return (PBounds) getFullBoundsReference().clone();
+    }
 
-			// 4. Now that my children's bounds are valid and my own bounds are valid run any
-			// layout algorithm here. Note that if you try to layout volatile children piccolo
-			// will most likely start a "soft" infinite loop. It won't freeze your program, but
-			// it will make an infinite number of calls to SwingUtilities invoke later. You don't
-			// want to do that.
-			layoutChildren();
-			
-			// 5. If the full bounds cache is invalid then recompute the full bounds cache
-			// here after our own bounds and the children's bounds have been computed above.
-			if (fullBoundsInvalid) {
-				double oldX = fullBoundsCache.x;
-				double oldY = fullBoundsCache.y;
-				double oldWidth = fullBoundsCache.width;
-				double oldHeight = fullBoundsCache.height;
-				boolean oldEmpty = fullBoundsCache.isEmpty();
-				
-				// 6. This will call getFullBoundsReference on all of the children. So if the above
-				// layoutChildren method changed the bounds of any of the children they will be
-				// validated again here.
-				fullBoundsCache = computeFullBounds(fullBoundsCache);
-				
-				boolean fullBoundsChanged = fullBoundsCache.x != oldX ||
-									fullBoundsCache.y != oldY ||
-									fullBoundsCache.width != oldWidth ||
-									fullBoundsCache.height != oldHeight ||
-									fullBoundsCache.isEmpty() != oldEmpty;	 
-									
-				// 7. If the new full bounds cache differs from the previous cache then
-				// tell our parent to invalidate their full bounds. This is how bounds changes
-				// deep in the tree percolate up.
-				if (fullBoundsChanged) {
-					if (parent != null) parent.invalidateFullBounds();
-					firePropertyChange(PROPERTY_CODE_FULL_BOUNDS, PROPERTY_FULL_BOUNDS, null, fullBoundsCache);
-	
-					// 8. If our paint was invalid make sure to repaint our old full bounds. The
-					// new bounds will be computed later in the validatePaint pass.
-					if (paintInvalid && !oldEmpty) {
-						TEMP_REPAINT_BOUNDS.setRect(oldX, oldY, oldWidth, oldHeight);
-						repaintFrom(TEMP_REPAINT_BOUNDS, this);
-					}
-				}
-			}
+    /**
+     * Return a reference to this node's full bounds cache. These bounds are
+     * stored in the parent coordinate system of this node and they include the
+     * union of this node's bounds and all the bounds of it's descendents. The
+     * bounds returned by this method should not be modified.
+     * 
+     * @return a reference to this node's full bounds cache.
+     */
+    public PBounds getFullBoundsReference() {
+        validateFullBounds();
+        return fullBoundsCache;
+    }
 
-			// 9. Clear the invalid bounds flags.			
-			boundsChanged = false;
-			fullBoundsInvalid = false;
-			childBoundsInvalid = false;
-		}
-		
-		return boundsVolatile || childBoundsVolatile;
-	}
+    /**
+     * Compute and return the full bounds of this node. If the dstBounds
+     * parameter is not null then it will be used to return the results instead
+     * of creating a new PBounds.
+     * 
+     * @param dstBounds if not null the new bounds will be stored here
+     * @return the full bounds in the parent coordinate system of this node
+     */
+    public PBounds computeFullBounds(PBounds dstBounds) {
+        PBounds result = getUnionOfChildrenBounds(dstBounds);
+        result.add(getBoundsReference());
+        localToParent(result);
+        return result;
+    }
 
-	/**
-	 * Nodes that apply layout constraints to their children should override
-	 * this method and do the layout there. 
-	 */
-	protected void layoutChildren() {
-	}
-			
-	//****************************************************************
-	// Node Transform - Methods to manipulate the node's transform.
-	// 
-	// Each node has a transform that is used to define the nodes
-	// local coordinate system. IE it is applied before picking and 
-	// rendering the node.
-	// 
-	// The usual way to move nodes about on the canvas is to manipulate
-	// this transform, as opposed to changing the bounds of the 
-	// node.
-	// 
-	// Since this transform defines the local coordinate system of this
-	// node the following methods with affect the global position both 
-	// this node and all of its descendents.
-	//****************************************************************
-		
-	/**
-	 * Returns the rotation applied by this node's transform in radians. 
-	 * This rotation affects this node and all its descendents. The value
-	 * returned will be between 0 and 2pi radians.
-	 * 
-	 * @return rotation in radians.
-	 */
-	public double getRotation() {
-		if (transform == null) return 0;
-		return transform.getRotation();
-	}
+    /**
+     * Compute and return the union of the full bounds of all the children of
+     * this node. If the dstBounds parameter is not null then it will be used to
+     * return the results instead of creating a new PBounds.
+     * 
+     * @param dstBounds if not null the new bounds will be stored here
+     */
+    public PBounds getUnionOfChildrenBounds(PBounds dstBounds) {
+        if (dstBounds == null) {
+            dstBounds = new PBounds();
+        }
+        else {
+            dstBounds.resetToZero();
+        }
 
-	/**
-	 * Sets the rotation of this nodes transform in radians. This will
-	 * affect this node and all its descendents.
-	 * 
-	 * @param theta rotation in radians
-	 */
-	public void setRotation(double theta) {
-		rotate(theta - getRotation());
-	}
+        int count = getChildrenCount();
+        for (int i = 0; i < count; i++) {
+            PNode each = (PNode) children.get(i);
+            dstBounds.add(each.getFullBoundsReference());
+        }
 
-	/**
-	 * Rotates this node by theta (in radians) about the 0,0 point. 
-	 * This will affect this node and all its descendents.
-	 * 
-	 * @param theta the amount to rotate by in radians
-	 */
-	public void rotate(double theta) {
-		rotateAboutPoint(theta, 0, 0);
-	}
+        return dstBounds;
+    }
 
-	/**
-	 * Rotates this node by theta (in radians), and then translates the node so
-	 * that the x, y position of its fullBounds stays constant.
-	 * 
-	 * @param theta the amount to rotate by in radians
-	 */
-	public void rotateInPlace(double theta) {
-		PBounds b = getFullBoundsReference();
-		double px = b.x;
-		double py = b.y;
-		rotateAboutPoint(theta, 0, 0);
-		b = getFullBoundsReference();
-		offset(px - b.x, py - b.y);
-	}
+    /**
+     * Return a copy of the full bounds of this node in the global coordinate
+     * system.
+     * 
+     * @return the full bounds in global coordinate system.
+     */
+    public PBounds getGlobalFullBounds() {
+        PBounds b = getFullBounds();
+        if (parent != null) {
+            parent.localToGlobal(b);
+        }
+        return b;
+    }
 
-	/**
-	 * Rotates this node by theta (in radians) about the given 
-	 * point. This will affect this node and all its descendents.
-	 * 
-	 * @param theta the amount to rotate by in radians
-	 */
-	public void rotateAboutPoint(double theta, Point2D point) {
-		rotateAboutPoint(theta, point.getX(), point.getY());
-	}
+    /**
+     * Return true if the full bounds of this node intersects with the specified
+     * bounds.
+     * 
+     * @param parentBounds the bounds to test for intersection against
+     *            (specified in parent's coordinate system)
+     * @return true if this nodes full bounds intersect the given bounds.
+     */
+    public boolean fullIntersects(Rectangle2D parentBounds) {
+        if (parentBounds == null)
+            return true;
+        return getFullBoundsReference().intersects(parentBounds);
+    }
 
-	/**
-	 * Rotates this node by theta (in radians) about the given 
-	 * point. This will affect this node and all its descendents.
-	 * 
-	 * @param theta the amount to rotate by in radians
-	 */
-	public void rotateAboutPoint(double theta, double x, double y) {
-		getTransformReference(true).rotate(theta, x, y);
-		invalidatePaint();
-		invalidateFullBounds();
-		firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
-	}
+    // ****************************************************************
+    // Bounds Damage Management - Methods used to invalidate and validate
+    // the bounds of nodes.
+    // ****************************************************************
 
-	/**
-	 * Return the total amount of rotation applied to this node by its own
-	 * transform together with the transforms of all its ancestors. The value
-	 * returned will be between 0 and 2pi radians.
-	 * 
-	 * @return the total amount of rotation applied to this node in radians
-	 */
-	public double getGlobalRotation() {
-		return getLocalToGlobalTransform(null).getRotation();
-	}
+    /**
+     * Return true if this nodes bounds may change at any time. The default
+     * behavior is to return false, subclasses that override this method to
+     * return true should also override getBoundsReference() and compute their
+     * volatile bounds there before returning the reference.
+     * 
+     * @return true if this node has volatile bounds
+     */
+    protected boolean getBoundsVolatile() {
+        return false;
+    }
 
-	/**
-	 * Set the global rotation (in radians) of this node. This is implemented by
-	 * rotating this nodes transform the required amount so that the nodes
-	 * global rotation is as requested.
-	 * 
-	 * @param theta the amount to rotate by in radians relative to the global coord system.
-	 */
-	public void setGlobalRotation(double theta) {
-		if (parent != null) {
-			setRotation(theta - parent.getGlobalRotation());
-		} else {
-			setRotation(theta);
-		}
-	}
+    /**
+     * Return true if this node has a child with volatile bounds.
+     * 
+     * @return true if this node has a child with volatile bounds
+     */
+    protected boolean getChildBoundsVolatile() {
+        return childBoundsVolatile;
+    }
 
-	/**
-	 * Return the scale applied by this node's transform. The scale is 
-	 * effecting this node and all its descendents.
-	 * 
-	 * @return scale applied by this nodes transform.
-	 */
-	public double getScale() {
-		if (transform == null) return 1;
-		return transform.getScale();
-	}
+    /**
+     * Set if this node has a child with volatile bounds. This should normally
+     * be managed automatically by the bounds validation process.
+     * 
+     * @param childBoundsVolatile true if this node has a descendent with
+     *            volatile bounds
+     */
+    protected void setChildBoundsVolatile(boolean childBoundsVolatile) {
+        this.childBoundsVolatile = childBoundsVolatile;
+    }
 
-	/**
-	 * Set the scale of this node's transform. The scale will 
-	 * affect this node and all its descendents.
-	 * 
-	 * @param scale the scale to set the transform to
-	 */
-	public void setScale(double scale) {
-		if (scale == 0) throw new RuntimeException("Can't set scale to 0");
-		scale(scale / getScale());
-	}
+    /**
+     * Return true if this node's bounds have recently changed. This flag will
+     * be reset on the next call of validateFullBounds.
+     * 
+     * @return true if this node's bounds have changed.
+     */
+    protected boolean getBoundsChanged() {
+        return boundsChanged;
+    }
+
+    /**
+     * Set the bounds chnaged flag. This flag will be reset on the next call of
+     * validateFullBounds.
+     * 
+     * @param boundsChanged true if this nodes bounds have changed.
+     */
+    protected void setBoundsChanged(boolean boundsChanged) {
+        this.boundsChanged = boundsChanged;
+    }
+
+    /**
+     * Return true if the full bounds of this node are invalid. This means that
+     * the full bounds of this node have changed and need to be recomputed.
+     * 
+     * @return true if the full bounds of this node are invalid
+     */
+    protected boolean getFullBoundsInvalid() {
+        return fullBoundsInvalid;
+    }
+
+    /**
+     * Set the full bounds invalid flag. This flag is set when the full bounds
+     * of this node need to be recomputed as is the case when this node is
+     * transformed or when one of this node's children changes geometry.
+     */
+    protected void setFullBoundsInvalid(boolean fullBoundsInvalid) {
+        this.fullBoundsInvalid = fullBoundsInvalid;
+    }
+
+    /**
+     * Return true if one of this node's descendents has invalid bounds.
+     */
+    protected boolean getChildBoundsInvalid() {
+        return childBoundsInvalid;
+    }
+
+    /**
+     * Set the flag indicating that one of this node's descendents has invalid
+     * bounds.
+     */
+    protected void setChildBoundsInvalid(boolean childBoundsInvalid) {
+        this.childBoundsInvalid = childBoundsInvalid;
+    }
+
+    /**
+     * This method should be called when the bounds of this node are changed. It
+     * invalidates the full bounds of this node, and also notifies each of this
+     * nodes children that their parent's bounds have changed. As a result of
+     * this method getting called this nodes layoutChildren will be called.
+     */
+    public void signalBoundsChanged() {
+        invalidateFullBounds();
+        setBoundsChanged(true);
+        firePropertyChange(PROPERTY_CODE_BOUNDS, PROPERTY_BOUNDS, null, bounds);
+
+        int count = getChildrenCount();
+        for (int i = 0; i < count; i++) {
+            PNode each = (PNode) children.get(i);
+            each.parentBoundsChanged();
+        }
+    }
+
+    /**
+     * Invalidate this node's layout, so that later layoutChildren will get
+     * called.
+     */
+    public void invalidateLayout() {
+        invalidateFullBounds();
+    }
+
+    /**
+     * A notification that the bounds of this node's parent have changed.
+     */
+    protected void parentBoundsChanged() {
+    }
+
+    /**
+     * Invalidates the full bounds of this node, and sets the child bounds
+     * invalid flag on each of this node's ancestors.
+     */
+    public void invalidateFullBounds() {
+        setFullBoundsInvalid(true);
+
+        PNode n = parent;
+        while (n != null && !n.getChildBoundsInvalid()) {
+            n.setChildBoundsInvalid(true);
+            n = n.parent;
+        }
+
+        if (SCENE_GRAPH_DELEGATE != null)
+            SCENE_GRAPH_DELEGATE.nodeFullBoundsInvalidated(this);
+    }
+
+    /**
+     * This method is called to validate the bounds of this node and all of its
+     * descendents. It returns true if this nodes bounds or the bounds of any of
+     * its descendents are marked as volatile.
+     * 
+     * @return true if this node or any of its descendents have volatile bounds
+     */
+    protected boolean validateFullBounds() {
+        boolean boundsVolatile = getBoundsVolatile();
+
+        // 1. Only compute new bounds if invalid flags are set.
+        if (fullBoundsInvalid || childBoundsInvalid || boundsVolatile || childBoundsVolatile) {
+
+            // 2. If my bounds are volatile and they have not been changed then
+            // signal a change.
+            //
+            // For most cases this will do nothing, but if a nodes bounds depend
+            // on its model, then
+            // validate bounds has the responsibility of making the bounds match
+            // the models value.
+            // For example PPaths validateBounds method makes sure that the
+            // bounds are equal to the
+            // bounds of the GeneralPath model.
+            if (boundsVolatile && !boundsChanged) {
+                signalBoundsChanged();
+            }
+
+            // 3. If the bounds of on of my decendents are invalidate then
+            // validate the bounds of all of my children.
+            if (childBoundsInvalid || childBoundsVolatile) {
+                childBoundsVolatile = false;
+                int count = getChildrenCount();
+                for (int i = 0; i < count; i++) {
+                    PNode each = (PNode) children.get(i);
+                    childBoundsVolatile |= each.validateFullBounds();
+                }
+            }
+
+            // 4. Now that my children's bounds are valid and my own bounds are
+            // valid run any layout algorithm here. Note that if you try to
+            // layout volatile
+            // children piccolo will most likely start a "soft" infinite loop.
+            // It won't freeze
+            // your program, but it will make an infinite number of calls to
+            // SwingUtilities
+            // invoke later. You don't want to do that.
+            layoutChildren();
+
+            // 5. If the full bounds cache is invalid then recompute the full
+            // bounds cache here after our own bounds and the children's bounds
+            // have been computed above.
+            if (fullBoundsInvalid) {
+                double oldX = fullBoundsCache.x;
+                double oldY = fullBoundsCache.y;
+                double oldWidth = fullBoundsCache.width;
+                double oldHeight = fullBoundsCache.height;
+                boolean oldEmpty = fullBoundsCache.isEmpty();
+
+                // 6. This will call getFullBoundsReference on all of the
+                // children. So if the above
+                // layoutChildren method changed the bounds of any of the
+                // children they will be
+                // validated again here.
+                fullBoundsCache = computeFullBounds(fullBoundsCache);
+
+                boolean fullBoundsChanged = fullBoundsCache.x != oldX || fullBoundsCache.y != oldY
+                        || fullBoundsCache.width != oldWidth || fullBoundsCache.height != oldHeight
+                        || fullBoundsCache.isEmpty() != oldEmpty;
+
+                // 7. If the new full bounds cache differs from the previous
+                // cache then
+                // tell our parent to invalidate their full bounds. This is how
+                // bounds changes
+                // deep in the tree percolate up.
+                if (fullBoundsChanged) {
+                    if (parent != null)
+                        parent.invalidateFullBounds();
+                    firePropertyChange(PROPERTY_CODE_FULL_BOUNDS, PROPERTY_FULL_BOUNDS, null, fullBoundsCache);
+
+                    // 8. If our paint was invalid make sure to repaint our old
+                    // full bounds. The
+                    // new bounds will be computed later in the validatePaint
+                    // pass.
+                    if (paintInvalid && !oldEmpty) {
+                        TEMP_REPAINT_BOUNDS.setRect(oldX, oldY, oldWidth, oldHeight);
+                        repaintFrom(TEMP_REPAINT_BOUNDS, this);
+                    }
+                }
+            }
+
+            // 9. Clear the invalid bounds flags.
+            boundsChanged = false;
+            fullBoundsInvalid = false;
+            childBoundsInvalid = false;
+        }
+
+        return boundsVolatile || childBoundsVolatile;
+    }
+
+    /**
+     * Nodes that apply layout constraints to their children should override
+     * this method and do the layout there.
+     */
+    protected void layoutChildren() {
+    }
+
+    // ****************************************************************
+    // Node Transform - Methods to manipulate the node's transform.
+    // 
+    // Each node has a transform that is used to define the nodes
+    // local coordinate system. IE it is applied before picking and
+    // rendering the node.
+    // 
+    // The usual way to move nodes about on the canvas is to manipulate
+    // this transform, as opposed to changing the bounds of the
+    // node.
+    // 
+    // Since this transform defines the local coordinate system of this
+    // node the following methods with affect the global position both
+    // this node and all of its descendents.
+    // ****************************************************************
+
+    /**
+     * Returns the rotation applied by this node's transform in radians. This
+     * rotation affects this node and all its descendents. The value returned
+     * will be between 0 and 2pi radians.
+     * 
+     * @return rotation in radians.
+     */
+    public double getRotation() {
+        if (transform == null)
+            return 0;
+        return transform.getRotation();
+    }
+
+    /**
+     * Sets the rotation of this nodes transform in radians. This will affect
+     * this node and all its descendents.
+     * 
+     * @param theta rotation in radians
+     */
+    public void setRotation(double theta) {
+        rotate(theta - getRotation());
+    }
+
+    /**
+     * Rotates this node by theta (in radians) about the 0,0 point. This will
+     * affect this node and all its descendents.
+     * 
+     * @param theta the amount to rotate by in radians
+     */
+    public void rotate(double theta) {
+        rotateAboutPoint(theta, 0, 0);
+    }
+
+    /**
+     * Rotates this node by theta (in radians), and then translates the node so
+     * that the x, y position of its fullBounds stays constant.
+     * 
+     * @param theta the amount to rotate by in radians
+     */
+    public void rotateInPlace(double theta) {
+        PBounds b = getFullBoundsReference();
+        double px = b.x;
+        double py = b.y;
+        rotateAboutPoint(theta, 0, 0);
+        b = getFullBoundsReference();
+        offset(px - b.x, py - b.y);
+    }
+
+    /**
+     * Rotates this node by theta (in radians) about the given point. This will
+     * affect this node and all its descendents.
+     * 
+     * @param theta the amount to rotate by in radians
+     */
+    public void rotateAboutPoint(double theta, Point2D point) {
+        rotateAboutPoint(theta, point.getX(), point.getY());
+    }
+
+    /**
+     * Rotates this node by theta (in radians) about the given point. This will
+     * affect this node and all its descendents.
+     * 
+     * @param theta the amount to rotate by in radians
+     */
+    public void rotateAboutPoint(double theta, double x, double y) {
+        getTransformReference(true).rotate(theta, x, y);
+        invalidatePaint();
+        invalidateFullBounds();
+        firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
+    }
+
+    /**
+     * Return the total amount of rotation applied to this node by its own
+     * transform together with the transforms of all its ancestors. The value
+     * returned will be between 0 and 2pi radians.
+     * 
+     * @return the total amount of rotation applied to this node in radians
+     */
+    public double getGlobalRotation() {
+        return getLocalToGlobalTransform(null).getRotation();
+    }
+
+    /**
+     * Set the global rotation (in radians) of this node. This is implemented by
+     * rotating this nodes transform the required amount so that the nodes
+     * global rotation is as requested.
+     * 
+     * @param theta the amount to rotate by in radians relative to the global
+     *            coord system.
+     */
+    public void setGlobalRotation(double theta) {
+        if (parent != null) {
+            setRotation(theta - parent.getGlobalRotation());
+        }
+        else {
+            setRotation(theta);
+        }
+    }
+
+    /**
+     * Return the scale applied by this node's transform. The scale is effecting
+     * this node and all its descendents.
+     * 
+     * @return scale applied by this nodes transform.
+     */
+    public double getScale() {
+        if (transform == null)
+            return 1;
+        return transform.getScale();
+    }
+
+    /**
+     * Set the scale of this node's transform. The scale will affect this node
+     * and all its descendents.
+     * 
+     * @param scale the scale to set the transform to
+     */
+    public void setScale(double scale) {
+        if (scale == 0)
+            throw new RuntimeException("Can't set scale to 0");
+        scale(scale / getScale());
+    }
+
+    /**
+     * Scale this nodes transform by the given amount. This will affect this
+     * node and all of its descendents.
+     * 
+     * @param scale the amount to scale by
+     */
+    public void scale(double scale) {
+        scaleAboutPoint(scale, 0, 0);
+    }
+
+    /**
+     * Scale this nodes transform by the given amount about the specified point.
+     * This will affect this node and all of its descendents.
+     * 
+     * @param scale the amount to scale by
+     * @param point the point to scale about
+     */
+    public void scaleAboutPoint(double scale, Point2D point) {
+        scaleAboutPoint(scale, point.getX(), point.getY());
+    }
+
+    /**
+     * Scale this nodes transform by the given amount about the specified point.
+     * This will affect this node and all of its descendents.
+     * 
+     * @param scale the amount to scale by
+     */
+    public void scaleAboutPoint(double scale, double x, double y) {
+        getTransformReference(true).scaleAboutPoint(scale, x, y);
+        invalidatePaint();
+        invalidateFullBounds();
+        firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
+    }
+
+    /**
+     * Return the global scale that is being applied to this node by its
+     * transform together with the transforms of all its ancestors.
+     */
+    public double getGlobalScale() {
+        return getLocalToGlobalTransform(null).getScale();
+    }
+
+    /**
+     * Set the global scale of this node. This is implemented by scaling this
+     * nodes transform the required amount so that the nodes global scale is as
+     * requested.
+     * 
+     * @param scale the desired global scale
+     */
+    public void setGlobalScale(double scale) {
+        if (parent != null) {
+            setScale(scale / parent.getGlobalScale());
+        }
+        else {
+            setScale(scale);
+        }
+    }
+
+    public double getXOffset() {
+        if (transform == null)
+            return 0;
+        return transform.getTranslateX();
+    }
+
+    public double getYOffset() {
+        if (transform == null)
+            return 0;
+        return transform.getTranslateY();
+    }
+
+    /**
+     * Return the offset that is being applied to this node by its transform.
+     * This offset effects this node and all of its descendents and is specified
+     * in the parent coordinate system. This returns the values that are in the
+     * m02 and m12 positions in the affine transform.
+     * 
+     * @return a point representing the x and y offset
+     */
+    public Point2D getOffset() {
+        if (transform == null)
+            return new Point2D.Double();
+        return new Point2D.Double(transform.getTranslateX(), transform.getTranslateY());
+    }
+
+    /**
+     * Set the offset that is being applied to this node by its transform. This
+     * offset effects this node and all of its descendents and is specified in
+     * the nodes parent coordinate system. This directly sets the values of the
+     * m02 and m12 positions in the affine transform. Unlike "PNode.translate()"
+     * it is not effected by the transforms scale.
+     * 
+     * @param point a point representing the x and y offset
+     */
+    public void setOffset(Point2D point) {
+        setOffset(point.getX(), point.getY());
+    }
+
+    /**
+     * Set the offset that is being applied to this node by its transform. This
+     * offset effects this node and all of its descendents and is specified in
+     * the nodes parent coordinate system. This directly sets the values of the
+     * m02 and m12 positions in the affine transform. Unlike "PNode.translate()"
+     * it is not effected by the transforms scale.
+     * 
+     * @param x amount of x offset
+     * @param y amount of y offset
+     */
+    public void setOffset(double x, double y) {
+        getTransformReference(true).setOffset(x, y);
+        invalidatePaint();
+        invalidateFullBounds();
+        firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
+    }
+
+    /**
+     * Offset this node relative to the parents coordinate system, and is NOT
+     * effected by this nodes current scale or rotation. This is implemented by
+     * directly adding dx to the m02 position and dy to the m12 position in the
+     * affine transform.
+     */
+    public void offset(double dx, double dy) {
+        getTransformReference(true);
+        setOffset(transform.getTranslateX() + dx, transform.getTranslateY() + dy);
+    }
+
+    /**
+     * Translate this node's transform by the given amount, using the standard
+     * affine transform translate method. This translation effects this node and
+     * all of its descendents.
+     */
+    public void translate(double dx, double dy) {
+        getTransformReference(true).translate(dx, dy);
+        invalidatePaint();
+        invalidateFullBounds();
+        firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
+    }
+
+    /**
+     * Return the global translation that is being applied to this node by its
+     * transform together with the transforms of all its ancestors.
+     */
+    public Point2D getGlobalTranslation() {
+        Point2D p = getOffset();
+        if (parent != null) {
+            parent.localToGlobal(p);
+        }
+        return p;
+    }
+
+    /**
+     * Set the global translation of this node. This is implemented by
+     * translating this nodes transform the required amount so that the nodes
+     * global scale is as requested.
+     * 
+     * @param globalPoint the desired global translation
+     */
+    public void setGlobalTranslation(Point2D globalPoint) {
+        if (parent != null) {
+            parent.getGlobalToLocalTransform(null).transform(globalPoint, globalPoint);
+        }
+        setOffset(globalPoint);
+    }
+
+    /**
+     * Transform this nodes transform by the given transform.
+     * 
+     * @param aTransform the transform to apply.
+     */
+    public void transformBy(AffineTransform aTransform) {
+        getTransformReference(true).concatenate(aTransform);
+        invalidatePaint();
+        invalidateFullBounds();
+        firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
+    }
+
+    /**
+     * Linearly interpolates between a and b, based on t. Specifically, it
+     * computes lerp(a, b, t) = a + t*(b - a). This produces a result that
+     * changes from a (when t = 0) to b (when t = 1).
+     * 
+     * @param a from point
+     * @param b to Point
+     * @param t variable 'time' parameter
+     */
+    static public double lerp(double t, double a, double b) {
+        return (a + t * (b - a));
+    }
+
+    /**
+     * This will calculate the necessary transform in order to make this node
+     * appear at a particular position relative to the specified bounding box.
+     * The source point specifies a point in the unit square (0, 0) - (1, 1)
+     * that represents an anchor point on the corresponding node to this
+     * transform. The destination point specifies an anchor point on the
+     * reference node. The position method then computes the transform that
+     * results in transforming this node so that the source anchor point
+     * coincides with the reference anchor point. This can be useful for layout
+     * algorithms as it is straightforward to position one object relative to
+     * another.
+     * 
+     * Point2D srcPt = new Point2D.Double(1.0, 0.0);
+     * Point2D destPt = new Point2D.Double(0.0, 0.0);
+     * A.position(srcPt, destPt, B.getGlobalBounds(), 750, null);
+     * 
+     * 
+     * The result is that A will move so that its upper-right corner is at the
+     * same place as the upper-left corner of B, and the transition will be
+     * smoothly animated over a period of 750 milliseconds.
+     * 
+     * @param srcPt The anchor point on this transform's node (normalized to a
+     *            unit square)
+     * @param destPt The anchor point on destination bounds (normalized to a
+     *            unit square)
+     * @param destBounds The bounds (in global coordinates) used to calculate
+     *            this transform's node
+     * @param millis Number of milliseconds over which to perform the animation
+     */
+    public void position(Point2D srcPt, Point2D destPt, Rectangle2D destBounds, int millis) {
+        double srcx, srcy;
+        double destx, desty;
+        double dx, dy;
+        Point2D pt1, pt2;
+
+        if (parent != null) {
+            // First compute translation amount in global coordinates
+            Rectangle2D srcBounds = getGlobalFullBounds();
+            srcx = lerp(srcPt.getX(), srcBounds.getX(), srcBounds.getX() + srcBounds.getWidth());
+            srcy = lerp(srcPt.getY(), srcBounds.getY(), srcBounds.getY() + srcBounds.getHeight());
+            destx = lerp(destPt.getX(), destBounds.getX(), destBounds.getX() + destBounds.getWidth());
+            desty = lerp(destPt.getY(), destBounds.getY(), destBounds.getY() + destBounds.getHeight());
+
+            // Convert vector to local coordinates
+            pt1 = new Point2D.Double(srcx, srcy);
+            globalToLocal(pt1);
+            pt2 = new Point2D.Double(destx, desty);
+            globalToLocal(pt2);
+            dx = (pt2.getX() - pt1.getX());
+            dy = (pt2.getY() - pt1.getY());
+
+            // Finally, animate change
+            PAffineTransform at = new PAffineTransform(getTransformReference(true));
+            at.translate(dx, dy);
+            animateToTransform(at, millis);
+        }
+    }
+
+    /**
+     * Return a copy of the transform associated with this node.
+     * 
+     * @return copy of this node's transform
+     */
+    public PAffineTransform getTransform() {
+        if (transform == null) {
+            return new PAffineTransform();
+        }
+        else {
+            return (PAffineTransform) transform.clone();
+        }
+    }
+
+    /**
+     * Return a reference to the transform associated with this node. This
+     * returned transform should not be modified. PNode transforms are created
+     * lazily when needed. If you access the transform reference before the
+     * transform has been created it may return null. The
+     * createNewTransformIfNull parameter is used to specify that the PNode
+     * should create a new transform (and assign that transform to the nodes
+     * local transform variable) instead of returning null.
+     * 
+     * @return reference to this node's transform
+     */
+    public PAffineTransform getTransformReference(boolean createNewTransformIfNull) {
+        if (transform == null && createNewTransformIfNull) {
+            transform = new PAffineTransform();
+        }
+        return transform;
+    }
+
+    /**
+     * Return an inverted copy of the transform associated with this node.
+     * 
+     * @return inverted copy of this node's transform
+     */
+    public PAffineTransform getInverseTransform() {
+        if (transform == null) {
+            return new PAffineTransform();
+        }
+        else {
+            try {
+                return new PAffineTransform(transform.createInverse());
+            }
+            catch (NoninvertibleTransformException e) {
+                e.printStackTrace();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Set the transform applied to this node.
+     * 
+     * @param newTransform the new transform value
+     */
+    public void setTransform(AffineTransform newTransform) {
+        if (newTransform == null) {
+            transform = null;
+        }
+        else {
+            getTransformReference(true).setTransform(newTransform);
+        }
+
+        invalidatePaint();
+        invalidateFullBounds();
+        firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
+    }
+
+    // ****************************************************************
+    // Paint Damage Management - Methods used to invalidate the areas of
+    // the screen that this node appears in so that they will later get
+    // painted.
+    // 
+    // Generally you will not need to call these invalidate methods
+    // when starting out with Piccolo because methods such as setPaint
+    // already automatically call them for you. You will need to call
+    // them when you start creating your own nodes.
+    // 
+    // When you do create you own nodes the only method that you will
+    // normally need to call is invalidatePaint. This method marks the
+    // nodes as having invalid paint, the root node's UI cycle will then
+    // later discover this damage and report it to the Java repaint manager.
+    // 
+    // Repainting is normally done with PNode.invalidatePaint() instead of
+    // directly calling PNode.repaint() because PNode.repaint() requires
+    // the nodes bounds to be computed right away. But with invalidatePaint
+    // the bounds computation can be delayed until the end of the root's UI
+    // cycle, and this can add up to a bit savings when modifying a
+    // large number of nodes all at once.
+    // 
+    // The other methods here will rarely be called except internally
+    // from the framework.
+    // ****************************************************************
+
+    /**
+     * Return true if this nodes paint is invalid, in which case the node needs
+     * to be repainted.
+     * 
+     * @return true if this node needs to be repainted
+     */
+    public boolean getPaintInvalid() {
+        return paintInvalid;
+    }
+
+    /**
+     * Mark this node as having invalid paint. If this is set the node will
+     * later be repainted. Node this method is most often used internally.
+     * 
+     * @param paintInvalid true if this node should be repainted
+     */
+    public void setPaintInvalid(boolean paintInvalid) {
+        this.paintInvalid = paintInvalid;
+    }
+
+    /**
+     * Return true if this node has a child with invalid paint.
+     * 
+     * @return true if this node has a child with invalid paint
+     */
+    public boolean getChildPaintInvalid() {
+        return childPaintInvalid;
+    }
+
+    /**
+     * Mark this node as having a child with invalid paint.
+     * 
+     * @param childPaintInvalid true if this node has a child with invalid paint
+     */
+    public void setChildPaintInvalid(boolean childPaintInvalid) {
+        this.childPaintInvalid = childPaintInvalid;
+    }
+
+    /**
+     * Invalidate this node's paint, and mark all of its ancestors as having a
+     * node with invalid paint.
+     */
+    public void invalidatePaint() {
+        setPaintInvalid(true);
+
+        PNode n = parent;
+        while (n != null && !n.getChildPaintInvalid()) {
+            n.setChildPaintInvalid(true);
+            n = n.parent;
+        }
+
+        if (SCENE_GRAPH_DELEGATE != null)
+            SCENE_GRAPH_DELEGATE.nodePaintInvalidated(this);
+    }
+
+    /**
+     * Repaint this node and any of its descendents if they have invalid paint.
+     */
+    public void validateFullPaint() {
+        if (getPaintInvalid()) {
+            repaint();
+            setPaintInvalid(false);
+        }
+
+        if (getChildPaintInvalid()) {
+            int count = getChildrenCount();
+            for (int i = 0; i < count; i++) {
+                PNode each = (PNode) children.get(i);
+                each.validateFullPaint();
+            }
+            setChildPaintInvalid(false);
+        }
+    }
+
+    /**
+     * Mark the area on the screen represented by this nodes full bounds as
+     * needing a repaint.
+     */
+    public void repaint() {
+        TEMP_REPAINT_BOUNDS.setRect(getFullBoundsReference());
+        repaintFrom(TEMP_REPAINT_BOUNDS, this);
+    }
+
+    /**
+     * Pass the given repaint request up the tree, so that any cameras can
+     * invalidate that region on their associated canvas.
+     * 
+     * @param localBounds the bounds to repaint
+     * @param childOrThis if childOrThis does not equal this then this nodes
+     *            transform will be applied to the localBounds param
+     */
+    public void repaintFrom(PBounds localBounds, PNode childOrThis) {
+        if (parent != null) {
+            if (childOrThis != this) {
+                localToParent(localBounds);
+            }
+            else if (!getVisible()) {
+                return;
+            }
+            parent.repaintFrom(localBounds, this);
+        }
+    }
+
+    // ****************************************************************
+    // Occluding - Methods to suppor occluding optimization. Not yet
+    // complete.
+    // ****************************************************************
+
+    public boolean isOpaque(Rectangle2D boundary) {
+        return false;
+    }
+
+    public boolean getOccluded() {
+        return occluded;
+    }
+
+    public void setOccluded(boolean isOccluded) {
+        occluded = isOccluded;
+    }
+
+    // ****************************************************************
+    // Painting - Methods for painting this node and its children
+    // 
+    // Painting is how a node defines its visual representation on the
+    // screen, and is done in the local coordinate system of the node.
+    // 
+    // The default painting behavior is to first paint the node, and
+    // then paint the node's children on top of the node. If a node
+    // needs wants specialized painting behavior it can override:
+    // 
+    // paint() - Painting here will happen before the children
+    // are painted, so the children will be painted on top of painting done
+    // here.
+    // paintAfterChildren() - Painting here will happen after the children
+    // are painted, so it will paint on top of them.
+    // 
+    // Note that you should not normally need to override fullPaint().
+    // 
+    // The visible flag can be used to make a node invisible so that
+    // it will never get painted.
+    // ****************************************************************
+
+    /**
+     * Return true if this node is visible, that is if it will paint itself and
+     * descendents.
+     * 
+     * @return true if this node and its descendents are visible.
+     */
+    public boolean getVisible() {
+        return visible;
+    }
+
+    /**
+     * Set the visibility of this node and its descendents.
+     * 
+     * @param isVisible true if this node and its descendents are visible
+     */
+    public void setVisible(boolean isVisible) {
+        if (getVisible() != isVisible) {
+            if (!isVisible)
+                repaint();
+            visible = isVisible;
+            firePropertyChange(PROPERTY_CODE_VISIBLE, PROPERTY_VISIBLE, null, null);
+            invalidatePaint();
+        }
+    }
+
+    /**
+     * Return the paint used to paint this node. This value may be null.
+     */
+    public Paint getPaint() {
+        return paint;
+    }
+
+    /**
+     * Set the paint used to paint this node. This value may be set to null.
+     */
+    public void setPaint(Paint newPaint) {
+        if (paint == newPaint)
+            return;
+
+        Paint old = paint;
+        paint = newPaint;
+        invalidatePaint();
+        firePropertyChange(PROPERTY_CODE_PAINT, PROPERTY_PAINT, old, paint);
+    }
+
+    /**
+     * Return the transparency used when painting this node. Note that this
+     * transparency is also applied to all of the node's descendents.
+     */
+    public float getTransparency() {
+        return transparency;
+    }
+
+    /**
+     * Set the transparency used to paint this node. Note that this transparency
+     * applies to this node and all of its descendents.
+     */
+    public void setTransparency(float zeroToOne) {
+        if (transparency == zeroToOne)
+            return;
+
+        transparency = zeroToOne;
+        invalidatePaint();
+        firePropertyChange(PROPERTY_CODE_TRANSPARENCY, PROPERTY_TRANSPARENCY, null, null);
+    }
+
+    /**
+     * Paint this node behind any of its children nodes. Subclasses that define
+     * a different appearance should override this method and paint themselves
+     * there.
+     * 
+     * @param paintContext the paint context to use for painting the node
+     */
+    protected void paint(PPaintContext paintContext) {
+        if (paint != null) {
+            Graphics2D g2 = paintContext.getGraphics();
+            g2.setPaint(paint);
+            g2.fill(getBoundsReference());
+        }
+    }
+
+    /**
+     * Paint this node and all of its descendents. Most subclasses do not need
+     * to override this method, they should override paint or
+     * paintAfterChildren instead.
+     * 
+     * @param paintContext the paint context to use for painting this node and
+     *            its children
+     */
+    public void fullPaint(PPaintContext paintContext) {
+        if (getVisible() && fullIntersects(paintContext.getLocalClip())) {
+            paintContext.pushTransform(transform);
+            paintContext.pushTransparency(transparency);
+
+            if (!getOccluded())
+                paint(paintContext);
+
+            int count = getChildrenCount();
+            for (int i = 0; i < count; i++) {
+                PNode each = (PNode) children.get(i);
+                each.fullPaint(paintContext);
+            }
+
+            paintAfterChildren(paintContext);
 
-	/**
-	 * Scale this nodes transform by the given amount. This will affect this
-	 * node and all of its descendents.
-	 * 
-	 * @param scale the amount to scale by
-	 */
-	public void scale(double scale) {
-		scaleAboutPoint(scale, 0, 0);
-	}
+            paintContext.popTransparency(transparency);
+            paintContext.popTransform(transform);
+        }
+    }
 
-	/**
-	 * Scale this nodes transform by the given amount about the specified
-	 * point. This will affect this node and all of its descendents.
-	 * 
-	 * @param scale the amount to scale by
-	 * @param point the point to scale about
-	 */
-	public void scaleAboutPoint(double scale, Point2D point) {
-		scaleAboutPoint(scale, point.getX(), point.getY());
-	}
+    /**
+     * Subclasses that wish to do additional painting after their children are
+     * painted should override this method and do that painting here.
+     * 
+     * @param paintContext the paint context to sue for painting after the
+     *            children are painted
+     */
+    protected void paintAfterChildren(PPaintContext paintContext) {
+    }
 
-	/**
-	 * Scale this nodes transform by the given amount about the specified
-	 * point. This will affect this node and all of its descendents.
-	 * 
-	 * @param scale the amount to scale by
-	 */
-	public void scaleAboutPoint(double scale, double x, double y) {
-		getTransformReference(true).scaleAboutPoint(scale, x, y);
-		invalidatePaint();
-		invalidateFullBounds();
-		firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
-	}
+    /**
+     * Return a new Image representing this node and all of its children. The
+     * image size will be equal to the size of this nodes full bounds.
+     * 
+     * @return a new image representing this node and its descendents
+     */
+    public Image toImage() {
+        PBounds b = getFullBoundsReference();
+        return toImage((int) Math.ceil(b.getWidth()), (int) Math.ceil(b.getHeight()), null);
+    }
 
-	/**
-	 * Return the global scale that is being applied to this node by its transform
-	 * together with the transforms of all its ancestors.
-	 */ 
-	public double getGlobalScale() {
-		return getLocalToGlobalTransform(null).getScale();
-	}
+    /**
+     * Return a new Image of the requested size representing this node and all
+     * of its children. If backGroundPaint is null the resulting image will have
+     * transparent regions, else those regions will be filled with the
+     * backgroundPaint.
+     * 
+     * @param width pixel width of the resulting image
+     * @param height pixel height of the resulting image
+     * @return a new image representing this node and its descendents
+     */
+    public Image toImage(int width, int height, Paint backGroundPaint) {
+        PBounds imageBounds = getFullBounds();
 
-	/**
-	 * Set the global scale of this node. This is implemented by scaling
-	 * this nodes transform the required amount so that the nodes global scale
-	 * is as requested.
-	 * 
-	 * @param scale the desired global scale
-	 */
-	public void setGlobalScale(double scale) {
-		if (parent != null) {
-			setScale(scale / parent.getGlobalScale());
-		} else {
-			setScale(scale);
-		}
-	}
-	
-	public double getXOffset() {
-		if (transform == null) return 0;
-		return transform.getTranslateX();		
-	}
+        imageBounds.expandNearestIntegerDimensions();
 
-	public double getYOffset() {
-		if (transform == null) return 0;
-		return transform.getTranslateY();
-	}
-	
-	/**
-	 * Return the offset that is being applied to this node by its
-	 * transform. This offset effects this node and all of its descendents
-	 * and is specified in the parent coordinate system. This returns the
-	 * values that are in the m02 and m12 positions in the affine transform.
-	 * 
-	 * @return a point representing the x and y offset
-	 */
-	public Point2D getOffset() {
-		if (transform == null) return new Point2D.Double();
-		return new Point2D.Double(transform.getTranslateX(), transform.getTranslateY());
-	}
+        if (width / imageBounds.width < height / imageBounds.height) {
+            double scale = width / imageBounds.width;
+            height = (int) (imageBounds.height * scale);
+        }
+        else {
+            double scale = height / imageBounds.height;
+            width = (int) (imageBounds.width * scale);
+        }
 
-	/**
-	 * Set the offset that is being applied to this node by its
-	 * transform. This offset effects this node and all of its descendents and
-	 * is specified in the nodes parent coordinate system. This directly sets the values
-	 * of the m02 and m12 positions in the affine transform. Unlike "PNode.translate()" it
-	 * is not effected by the transforms scale.
-	 * 
-	 * @param point a point representing the x and y offset
-	 */
-	public void setOffset(Point2D point) {
-		setOffset(point.getX(), point.getY());
-	}
+        GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment()
+                .getDefaultScreenDevice().getDefaultConfiguration();
+        BufferedImage result = graphicsConfiguration.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
 
-	/**
-	 * Set the offset that is being applied to this node by its
-	 * transform. This offset effects this node and all of its descendents and
-	 * is specified in the nodes parent coordinate system. This directly sets the values
-	 * of the m02 and m12 positions in the affine transform. Unlike "PNode.translate()" it
-	 * is not effected by the transforms scale.
-	 * 
-	 * @param x amount of x offset
-	 * @param y amount of y offset
-	 */
-	public void setOffset(double x, double y) {
-		getTransformReference(true).setOffset(x, y);
-		invalidatePaint();
-		invalidateFullBounds();
-		firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
-	}
+        return toImage(result, backGroundPaint);
+    }
 
-	/**
-	 * Offset this node relative to the parents coordinate system, and is NOT
-	 * effected by this nodes current scale or rotation. This is implemented
-	 * by directly adding dx to the m02 position and dy to the m12 position in the
-	 * affine transform.
-	 */
-	public void offset(double dx, double dy) {
-		getTransformReference(true);
-		setOffset(transform.getTranslateX() + dx,
-				  transform.getTranslateY() + dy);		
-	}
+    /**
+     * Paint a representation of this node into the specified buffered image. If
+     * background, paint is null, then the image will not be filled with a color
+     * prior to rendering
+     * 
+     * @return a rendering of this image and its descendents into the specified
+     *         image
+     */
+    public Image toImage(BufferedImage image, Paint backGroundPaint) {
+        int width = image.getWidth();
+        int height = image.getHeight();
+        Graphics2D g2 = image.createGraphics();
 
-	/**
-	 * Translate this node's transform by the given amount, using the standard affine
-	 * transform translate method. This translation effects this node and all of its 
-	 * descendents.
-	 */
-	public void translate(double dx, double dy) {
-		getTransformReference(true).translate(dx, dy);
-		invalidatePaint();
-		invalidateFullBounds();
-		firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
-	}
+        if (backGroundPaint != null) {
+            g2.setPaint(backGroundPaint);
+            g2.fillRect(0, 0, width, height);
+        }
 
-	/**
-	 * Return the global translation that is being applied to this node by its transform
-	 * together with the transforms of all its ancestors.
-	 */ 
-	public Point2D getGlobalTranslation() {
-		Point2D p = getOffset();
-		if (parent != null) {
-			parent.localToGlobal(p);
-		}
-		return p;
-	}
+        // reuse print method
+        Paper paper = new Paper();
+        paper.setSize(width, height);
+        paper.setImageableArea(0, 0, width, height);
+        PageFormat pageFormat = new PageFormat();
+        pageFormat.setPaper(paper);
+        print(g2, pageFormat, 0);
 
-	/**
-	 * Set the global translation of this node. This is implemented by translating
-	 * this nodes transform the required amount so that the nodes global scale
-	 * is as requested.
-	 * 
-	 * @param globalPoint the desired global translation
-	 */
-	public void setGlobalTranslation(Point2D globalPoint) {
-		if (parent != null) {
-			parent.getGlobalToLocalTransform(null).transform(globalPoint, globalPoint);
-		}
-		setOffset(globalPoint);
-	}
-	
-	/**
-	 * Transform this nodes transform by the given transform.
-	 * 
-	 * @param aTransform the transform to apply.
-	 */
-	public void transformBy(AffineTransform aTransform) {
-		getTransformReference(true).concatenate(aTransform);
-		invalidatePaint();
-		invalidateFullBounds();
-		firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
-	}
+        return image;
+    }
 
-	/**
-	 * Linearly interpolates between a and b, based on t.
-	 * Specifically, it computes lerp(a, b, t) = a + t*(b - a).
-	 * This produces a result that changes from a (when t = 0) to b (when t = 1).
-	 *
-	 * @param a from point
-	 * @param b to Point
-	 * @param t variable 'time' parameter
-	 */
-	static public double lerp(double t, double a, double b) {
-		return (a + t * (b - a));
-	}
-	
-	/**
-	 * This will calculate the necessary transform in order to make this
-	 * node appear at a particular position relative to the
-	 * specified bounding box.  The source point specifies a point in the
-	 * unit square (0, 0) - (1, 1) that represents an anchor point on the
-	 * corresponding node to this transform.  The destination point specifies
-	 * an anchor point on the reference node.  The position method then
-	 * computes the transform that results in transforming this node so that
-	 * the source anchor point coincides with the reference anchor
-	 * point. This can be useful for layout algorithms as it is
-	 * straightforward to position one object relative to another.
-	 * 
-	 *     Point2D srcPt = new Point2D.Double(1.0, 0.0);
-	 *     Point2D destPt = new Point2D.Double(0.0, 0.0);
-	 *     A.position(srcPt, destPt, B.getGlobalBounds(), 750, null);
-	 * 
-	 * The result is that A will move so that its upper-right corner is at
-	 * the same place as the upper-left corner of B, and the transition will
-	 * be smoothly animated over a period of 750 milliseconds.
-	 * @param srcPt The anchor point on this transform's node (normalized to a unit square)
-	 * @param destPt The anchor point on destination bounds (normalized to a unit square)
-	 * @param destBounds The bounds (in global coordinates) used to calculate this transform's node
-	 * @param millis Number of milliseconds over which to perform the animation
-	 */
-	public void position(Point2D srcPt, Point2D destPt, Rectangle2D destBounds, int millis) {
-		double srcx, srcy;
-		double destx, desty;
-		double dx, dy;
-		Point2D pt1, pt2;
+    /**
+     * Constructs a new PrinterJob, allows the user to select which printer to
+     * print to, And then prints the node.
+     */
+    public void print() {
+        PrinterJob printJob = PrinterJob.getPrinterJob();
+        PageFormat pageFormat = printJob.defaultPage();
+        Book book = new Book();
+        book.append(this, pageFormat);
+        printJob.setPageable(book);
 
-		if (parent != null) {
-								// First compute translation amount in global coordinates
-			Rectangle2D srcBounds = getGlobalFullBounds();
-			srcx  = lerp(srcPt.getX(),  srcBounds.getX(),  srcBounds.getX() +  srcBounds.getWidth());
-			srcy  = lerp(srcPt.getY(),  srcBounds.getY(),  srcBounds.getY() +  srcBounds.getHeight());
-			destx = lerp(destPt.getX(), destBounds.getX(), destBounds.getX() + destBounds.getWidth());
-			desty = lerp(destPt.getY(), destBounds.getY(), destBounds.getY() + destBounds.getHeight());
+        if (printJob.printDialog()) {
+            try {
+                printJob.print();
+            }
+            catch (Exception e) {
+                System.out.println("Error Printing");
+                e.printStackTrace();
+            }
+        }
+    }
 
-								// Convert vector to local coordinates
-			pt1 = new Point2D.Double(srcx, srcy);
-			globalToLocal(pt1);
-			pt2 = new Point2D.Double(destx, desty);
-			globalToLocal(pt2);
-			dx = (pt2.getX() - pt1.getX());
-			dy = (pt2.getY() - pt1.getY());
+    /**
+     * Prints the node into the given Graphics context using the specified
+     * format. The zero based index of the requested page is specified by
+     * pageIndex. If the requested page does not exist then this method returns
+     * NO_SUCH_PAGE; otherwise PAGE_EXISTS is returned. If the printable object
+     * aborts the print job then it throws a PrinterException.
+     * 
+     * @param graphics the context into which the node is drawn
+     * @param pageFormat the size and orientation of the page
+     * @param pageIndex the zero based index of the page to be drawn
+     */
+    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) {
+        if (pageIndex != 0) {
+            return NO_SUCH_PAGE;
+        }
 
-								// Finally, animate change
-			PAffineTransform at = new PAffineTransform(getTransformReference(true));
-			at.translate(dx, dy);
-			animateToTransform(at, millis);
-		}
-	}
+        Graphics2D g2 = (Graphics2D) graphics;
+        PBounds imageBounds = getFullBounds();
 
+        imageBounds.expandNearestIntegerDimensions();
 
-	/**
-	 * Return a copy of the transform associated with this node.
-	 * 
-	 * @return copy of this node's transform
-	 */ 	
-	public PAffineTransform getTransform() {
-		if (transform == null) {
-			return new PAffineTransform();
-		} else {
-			return (PAffineTransform) transform.clone();
-		}
-	}
+        g2.setClip(0, 0, (int) pageFormat.getWidth(), (int) pageFormat.getHeight());
+        g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
 
-	/**
-	 * Return a reference to the transform associated with this node.
-	 * This returned transform should not be modified. PNode transforms are
-	 * created lazily when needed. If you access the transform reference
-	 * before the transform has been created it may return null. The 
-	 * createNewTransformIfNull parameter is used to specify that the PNode
-	 * should create a new transform (and assign that transform to the nodes
-	 * local transform variable) instead of returning null.
-	 * 
-	 * @return reference to this node's transform
-	 */ 	
-	public PAffineTransform getTransformReference(boolean createNewTransformIfNull) {
-		if (transform == null && createNewTransformIfNull) {
-			transform = new PAffineTransform();
-		}
-		return transform;
-	}
+        // scale the graphics so node's full bounds fit in the imageable bounds.
+        double scale = pageFormat.getImageableWidth() / imageBounds.getWidth();
+        if (pageFormat.getImageableHeight() / imageBounds.getHeight() < scale) {
+            scale = pageFormat.getImageableHeight() / imageBounds.getHeight();
+        }
 
-	/**
-	 * Return an inverted copy of the transform associated with this node.
-	 * 
-	 * @return inverted copy of this node's transform
-	 */ 	
-	public PAffineTransform getInverseTransform() {
-		if (transform == null) {
-			return new PAffineTransform();
-		} else {
-			try {
-				return new PAffineTransform(transform.createInverse());
-			} catch (NoninvertibleTransformException e) {
-				e.printStackTrace();
-			}
-			return null;
-		}
-	}
-	
-	/**
-	 * Set the transform applied to this node.
-	 * 
-	 * @param newTransform the new transform value
-	 */
-	public void setTransform(AffineTransform newTransform) {
-		if (newTransform == null) {
-			transform = null;
-		} else {
-			getTransformReference(true).setTransform(newTransform);
-		}
-		
-		invalidatePaint();
-		invalidateFullBounds();
-		firePropertyChange(PROPERTY_CODE_TRANSFORM, PROPERTY_TRANSFORM, null, transform);
-	}
-	
-	//****************************************************************
-	// Paint Damage Management - Methods used to invalidate the areas of 
-	// the screen that this node appears in so that they will later get 
-	// painted.
-	// 
-	// Generally you will not need to call these invalidate methods 
-	// when starting out with Piccolo because methods such as setPaint
-	// already automatically call them for you. You will need to call
-	// them when you start creating your own nodes.
-	// 
-	// When you do create you own nodes the only method that you will
-	// normally need to call is invalidatePaint. This method marks the 
-	// nodes as having invalid paint, the root node's UI cycle will then 
-	// later discover this damage and report it to the Java repaint manager.
-	// 
-	// Repainting is normally done with PNode.invalidatePaint() instead of 
-	// directly calling PNode.repaint() because PNode.repaint() requires 
-	// the nodes bounds to be computed right away. But with invalidatePaint 
-	// the bounds computation can be delayed until the end of the root's UI
-	// cycle, and this can add up to a bit savings when modifying a
-	// large number of nodes all at once.
-	// 
-	// The other methods here will rarely be called except internally
-	// from the framework.
-	//****************************************************************
-	
-	/**
-	 * Return true if this nodes paint is invalid, in which case the node 
-	 * needs to be repainted.
-	 * 
-	 * @return true if this node needs to be repainted
-	 */
-	public boolean getPaintInvalid() {
-		return paintInvalid;
-	}
+        g2.scale(scale, scale);
+        g2.translate(-imageBounds.x, -imageBounds.y);
 
-	/**
-	 * Mark this node as having invalid paint. If this is set the node 
-	 * will later be repainted. Node this method is most often 
-	 * used internally.
-	 * 
-	 * @param paintInvalid true if this node should be repainted
-	 */
-	public void setPaintInvalid(boolean paintInvalid) {
-		this.paintInvalid = paintInvalid;
-	}
-	
-	/**
-	 * Return true if this node has a child with invalid paint.
-	 * 
-	 * @return true if this node has a child with invalid paint
-	 */
-	public boolean getChildPaintInvalid() {
-		return childPaintInvalid;
-	}
+        PPaintContext pc = new PPaintContext(g2);
+        pc.setRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
+        fullPaint(pc);
 
-	/**
-	 * Mark this node as having a child with invalid paint. 
-	 * 
-	 * @param childPaintInvalid true if this node has a child with invalid paint
-	 */
-	public void setChildPaintInvalid(boolean childPaintInvalid) {
-		this.childPaintInvalid = childPaintInvalid;
-	}
-	
-	/**
-	 * Invalidate this node's paint, and mark all of its ancestors as having a node
-	 * with invalid paint.
-	 */
-	public void invalidatePaint() {
-		setPaintInvalid(true);
+        return PAGE_EXISTS;
+    }
 
-		PNode n = parent;
-		while (n != null && !n.getChildPaintInvalid()) {
-			n.setChildPaintInvalid(true);
-			n = n.parent;
-		}
-		
-		if (SCENE_GRAPH_DELEGATE != null) SCENE_GRAPH_DELEGATE.nodePaintInvalidated(this);
-	}
-	
-	/**
-	 * Repaint this node and any of its descendents if they have invalid paint.
-	 */
-	public void validateFullPaint() {		
-		if (getPaintInvalid()) {
-			repaint();
-			setPaintInvalid(false);
-		}
-		
-		if (getChildPaintInvalid()) {
-			int count = getChildrenCount();
-			for (int i = 0; i < count; i++) {
-				PNode each = (PNode) children.get(i);
-				each.validateFullPaint();
-			}			
-			setChildPaintInvalid(false);
-		}	
-	}
+    // ****************************************************************
+    // Picking - Methods for picking this node and its children.
+    // 
+    // Picking is used to determine the node that intersects a point or
+    // rectangle on the screen. It is most frequently used by the
+    // PInputManager to determine the node that the cursor is over.
+    // 
+    // The intersects() method is used to determine if a node has
+    // been picked or not. The default implementation just test to see
+    // if the pick bounds intersects the bounds of the node. Subclasses
+    // whose geometry (a circle for example) does not match up exactly with
+    // the bounds should override the intersects() method.
+    // 
+    // The default picking behavior is to first try to pick the nodes
+    // children, and then try to pick the nodes own bounds. If a node
+    // wants specialized picking behavior it can override:
+    // 
+    // pick() - Pick nodes here that should be picked before the nodes
+    // children are picked.
+    // pickAfterChildren() - Pick nodes here that should be picked after the
+    // node's children are picked.
+    // 
+    // Note that fullPick should not normally be overridden.
+    // 
+    // The pickable and childrenPickable flags can be used to make a
+    // node or it children not pickable even if their geometry does
+    // intersect the pick bounds.
+    // ****************************************************************
 
-	/**
-	 * Mark the area on the screen represented by this nodes full bounds 
-	 * as needing a repaint.
-	 */
-	public void repaint() {
-		TEMP_REPAINT_BOUNDS.setRect(getFullBoundsReference());
-		repaintFrom(TEMP_REPAINT_BOUNDS, this);
-	}
+    /**
+     * Return true if this node is pickable. Only pickable nodes can receive
+     * input events. Nodes are pickable by default.
+     * 
+     * @return true if this node is pickable
+     */
+    public boolean getPickable() {
+        return pickable;
+    }
 
-	/**
-	 * Pass the given repaint request up the tree, so that any cameras
-	 * can invalidate that region on their associated canvas.
-	 * 
-	 * @param localBounds the bounds to repaint
-	 * @param childOrThis if childOrThis does not equal this then this nodes transform will be applied to the localBounds param 
-	 */
-	public void repaintFrom(PBounds localBounds, PNode childOrThis) {
-		if (parent != null) {
-			if (childOrThis != this) {
-				localToParent(localBounds);
-			} else if (!getVisible()) {
-				return;
-			}
-			parent.repaintFrom(localBounds, this);
-		}
-	}
+    /**
+     * Set the pickable flag for this node. Only pickable nodes can receive
+     * input events. Nodes are pickable by default.
+     * 
+     * @param isPickable true if this node is pickable
+     */
+    public void setPickable(boolean isPickable) {
+        if (getPickable() != isPickable) {
+            pickable = isPickable;
+            firePropertyChange(PROPERTY_CODE_PICKABLE, PROPERTY_PICKABLE, null, null);
+        }
+    }
 
-	//****************************************************************
-	// Occluding - Methods to suppor occluding optimization. Not yet
-	// complete.
-	//****************************************************************
+    /**
+     * Return true if the children of this node should be picked. If this flag
+     * is false then this node will not try to pick its children. Children are
+     * pickable by default.
+     * 
+     * @return true if this node tries to pick its children
+     */
+    public boolean getChildrenPickable() {
+        return childrenPickable;
+    }
 
-	public boolean isOpaque(Rectangle2D boundary) {
-		return false;
-	}
-	
-	public boolean getOccluded() {
-		return occluded;
-	}
+    /**
+     * Set the children pickable flag. If this flag is false then this node will
+     * not try to pick its children. Children are pickable by default.
+     * 
+     * @param areChildrenPickable true if this node tries to pick its children
+     */
+    public void setChildrenPickable(boolean areChildrenPickable) {
+        if (getChildrenPickable() != areChildrenPickable) {
+            childrenPickable = areChildrenPickable;
+            firePropertyChange(PROPERTY_CODE_CHILDREN_PICKABLE, PROPERTY_CHILDREN_PICKABLE, null, null);
+        }
+    }
 
-	public void setOccluded(boolean isOccluded) {
-		occluded = isOccluded;
-	}
+    /**
+     * Try to pick this node before its children have had a chance to be picked.
+     * Nodes that paint on top of their children may want to override this
+     * method to if the pick path intersects that paint.
+     * 
+     * @param pickPath the pick path used for the pick operation
+     * @return true if this node was picked
+     */
+    protected boolean pick(PPickPath pickPath) {
+        return false;
+    }
 
-	//****************************************************************
-	// Painting - Methods for painting this node and its children
-	// 
-	// Painting is how a node defines its visual representation on the
-	// screen, and is done in the local coordinate system of the node.
-	// 
-	// The default painting behavior is to first paint the node, and 
-	// then paint the node's children on top of the node. If a node
-	// needs wants specialized painting behavior it can override:
-	// 
-	// paint() - Painting here will happen before the children
-	// are painted, so the children will be painted on top of painting done
-	// here.
-	// paintAfterChildren() - Painting here will happen after the children
-	// are painted, so it will paint on top of them.
-	// 
-	// Note that you should not normally need to override fullPaint().
-	// 
-	// The visible flag can be used to make a node invisible so that 
-	// it will never get painted.
-	//****************************************************************
+    /**
+     * Try to pick this node and all of its descendents. Most subclasses should
+     * not need to override this method. Instead they should override
+     * pick or pickAfterChildren.
+     * 
+     * @param pickPath the pick path to add the node to if its picked
+     * @return true if this node or one of its descendents was picked.
+     */
+    public boolean fullPick(PPickPath pickPath) {
+        if ((getPickable() || getChildrenPickable()) && fullIntersects(pickPath.getPickBounds())) {
+            pickPath.pushNode(this);
+            pickPath.pushTransform(transform);
 
-	/**
-	 * Return true if this node is visible, that is if it will paint itself
-	 * and descendents.
-	 * 
-	 * @return true if this node and its descendents are visible.
-	 */
-	public boolean getVisible() {
-		return visible;
-	}
+            boolean thisPickable = getPickable() && pickPath.acceptsNode(this);
 
-	/**
-	 * Set the visibility of this node and its descendents.
-	 * 
-	 * @param isVisible true if this node and its descendents are visible
-	 */
-	public void setVisible(boolean isVisible) {
-		if (getVisible() != isVisible) {
-			if (!isVisible) repaint();
-			visible = isVisible;
-			firePropertyChange(PROPERTY_CODE_VISIBLE ,PROPERTY_VISIBLE, null, null);
-			invalidatePaint();
-		}	
-	}
+            if (thisPickable) {
+                if (pick(pickPath)) {
+                    return true;
+                }
+            }
 
-	/**
-	 * Return the paint used to paint this node. This value may be null.
-	 */
-	public Paint getPaint() {
-		return paint;
-	}
+            if (getChildrenPickable()) {
+                int count = getChildrenCount();
+                for (int i = count - 1; i >= 0; i--) {
+                    PNode each = (PNode) children.get(i);
+                    if (each.fullPick(pickPath))
+                        return true;
+                }
+            }
 
-	/**
-	 * Set the paint used to paint this node. This value may be set to null.
-	 */
-	public void setPaint(Paint newPaint) {
-		if (paint == newPaint) return;
-		
-		Paint old = paint;
-		paint = newPaint;
-		invalidatePaint();
-		firePropertyChange(PROPERTY_CODE_PAINT ,PROPERTY_PAINT, old, paint);
-	}
+            if (thisPickable) {
+                if (pickAfterChildren(pickPath)) {
+                    return true;
+                }
+            }
 
-	/**
-	 * Return the transparency used when painting this node. Note that this
-	 * transparency is also applied to all of the node's descendents.
-	 */
-	public float getTransparency() {
-		return transparency;
-	}
+            pickPath.popTransform(transform);
+            pickPath.popNode(this);
+        }
 
-	/**
-	 * Set the transparency used to paint this node. Note that this transparency
-	 * applies to this node and all of its descendents.
-	 */
-	public void setTransparency(float zeroToOne) {
-		if (transparency == zeroToOne) return;
-		
-		transparency = zeroToOne;
-		invalidatePaint();
-		firePropertyChange(PROPERTY_CODE_TRANSPARENCY, PROPERTY_TRANSPARENCY, null, null);
-	}
+        return false;
+    }
 
-	/**
-	 * Paint this node behind any of its children nodes. Subclasses that define 
-	 * a different appearance should override this method and paint themselves 
-	 * there.
-	 * 
-	 * @param paintContext the paint context to use for painting the node
-	 */
-	protected void paint(PPaintContext paintContext) {
-		if (paint != null) {
-			Graphics2D g2 = paintContext.getGraphics();
-			g2.setPaint(paint);
-			g2.fill(getBoundsReference());
-		}
-	}
+    public void findIntersectingNodes(Rectangle2D fullBounds, ArrayList results) {
+        if (fullIntersects(fullBounds)) {
+            Rectangle2D localBounds = parentToLocal((Rectangle2D) fullBounds.clone());
 
-	/**
-	 * Paint this node and all of its descendents. Most subclasses do not need to
-	 * override this method, they should override paint or
-	 * paintAfterChildren instead.
-	 * 
-	 * @param paintContext the paint context to use for painting this node and its children
-	 */
-	public void fullPaint(PPaintContext paintContext) {
-		if (getVisible() && fullIntersects(paintContext.getLocalClip())) {			
-			paintContext.pushTransform(transform);
-			paintContext.pushTransparency(transparency);
+            if (intersects(localBounds)) {
+                results.add(this);
+            }
 
-			if (!getOccluded())
-				paint(paintContext);
-			
-			int count = getChildrenCount();
-			for (int i = 0; i < count; i++) {
-				PNode each = (PNode) children.get(i);
-				each.fullPaint(paintContext);
-			}				
+            int count = getChildrenCount();
+            for (int i = count - 1; i >= 0; i--) {
+                PNode each = (PNode) children.get(i);
+                each.findIntersectingNodes(localBounds, results);
+            }
+        }
+    }
 
-			paintAfterChildren(paintContext);
-			
-			paintContext.popTransparency(transparency);
-			paintContext.popTransform(transform);
-		}
-	}
+    /**
+     * Try to pick this node after its children have had a chance to be picked.
+     * Most subclasses the define a different geometry will need to override
+     * this method.
+     * 
+     * @param pickPath the pick path used for the pick operation
+     * @return true if this node was picked
+     */
+    protected boolean pickAfterChildren(PPickPath pickPath) {
+        if (intersects(pickPath.getPickBounds())) {
+            return true;
+        }
+        return false;
+    }
 
-	/**
-	 * Subclasses that wish to do additional painting after their children
-	 * are painted should override this method and do that painting here.
-	 * 
-	 * @param paintContext the paint context to sue for painting after the children are painted
-	 */
-	protected void paintAfterChildren(PPaintContext paintContext) {
-	}
+    // ****************************************************************
+    // Structure - Methods for manipulating and traversing the
+    // parent child relationship
+    // 
+    // Most of these methods won't need to be overridden by subclasses
+    // but you will use them frequently to build up your node structures.
+    // ****************************************************************
 
-	/**
-	 * Return a new Image representing this node and all of its children. The image size will
-	 * be equal to the size of this nodes full bounds.
-	 * 
-	 * @return a new image representing this node and its descendents
-	 */
-	public Image toImage() {
-		PBounds b = getFullBoundsReference();
-		return toImage((int) Math.ceil(b.getWidth()), (int) Math.ceil(b.getHeight()), null);
-	}
+    /**
+     * Add a node to be a new child of this node. The new node is added to the
+     * end of the list of this node's children. If child was previously a child
+     * of another node, it is removed from that first.
+     * 
+     * @param child the new child to add to this node
+     */
+    public void addChild(PNode child) {
+        int insertIndex = getChildrenCount();
+        if (child.parent == this)
+            insertIndex--;
+        addChild(insertIndex, child);
+    }
 
-	/**
-	 * Return a new Image of the requested size representing this 
-	 * node and all of its children. If backGroundPaint is null the resulting
-	 * image will have transparent regions, else those regions will be filled
-	 * with the backgroundPaint.
-	 *	
-	 * @param width pixel width of the resulting image
-	 * @param height pixel height of the resulting image
-	 * @return a new image representing this node and its descendents
-	 */
-	public Image toImage(int width, int height, Paint backGroundPaint) {
-		PBounds imageBounds = getFullBounds();		
+    /**
+     * Add a node to be a new child of this node at the specified index. If
+     * child was previously a child of another node, it is removed from that
+     * node first.
+     * 
+     * @param child the new child to add to this node
+     */
+    public void addChild(int index, PNode child) {
+        PNode oldParent = child.getParent();
 
-		imageBounds.expandNearestIntegerDimensions();	
-		
-		if(width / imageBounds.width < height / imageBounds.height) {
-			double scale = width / imageBounds.width;
-			height = (int) (imageBounds.height * scale);
-		} else {
-			double scale = height / imageBounds.height;
-			width = (int) (imageBounds.width * scale);
-		}
+        if (oldParent != null) {
+            oldParent.removeChild(child);
+        }
 
-		GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
-		BufferedImage result = graphicsConfiguration.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
+        child.setParent(this);
+        getChildrenReference().add(index, child);
+        child.invalidatePaint();
+        invalidateFullBounds();
 
-		return toImage(result, backGroundPaint);
-	}
+        firePropertyChange(PROPERTY_CODE_CHILDREN, PROPERTY_CHILDREN, null, children);
+    }
 
-	/**
-	 * Paint a representation of this node into the specified buffered image.  If background,
-	 * paint is null, then the image will not be filled with a color prior to rendering  
-	 *
-	 * @return a rendering of this image and its descendents into the specified image
-	 */
-	public Image toImage(BufferedImage image, Paint backGroundPaint) {
-	    int width = image.getWidth();
-	    int height = image.getHeight();	    
-		Graphics2D g2 = image.createGraphics();	
+    /**
+     * Add a collection of nodes to be children of this node. If these nodes
+     * already have parents they will first be removed from those parents.
+     * 
+     * @param nodes a collection of nodes to be added to this node
+     */
+    public void addChildren(Collection nodes) {
+        Iterator i = nodes.iterator();
+        while (i.hasNext()) {
+            PNode each = (PNode) i.next();
+            addChild(each);
+        }
+    }
 
-		if (backGroundPaint != null) {
-			g2.setPaint(backGroundPaint);
-			g2.fillRect(0, 0, width, height);
-		}
-		
-		// reuse print method
-		Paper paper = new Paper();
-		paper.setSize(width, height);
-		paper.setImageableArea(0, 0, width, height);
-		PageFormat pageFormat = new PageFormat();
-		pageFormat.setPaper(paper);
-		print(g2, pageFormat, 0);
+    /**
+     * Return true if this node is an ancestor of the parameter node.
+     * 
+     * @param node a possible descendent node
+     * @return true if this node is an ancestor of the given node
+     */
+    public boolean isAncestorOf(PNode node) {
+        PNode p = node.parent;
+        while (p != null) {
+            if (p == this)
+                return true;
+            p = p.parent;
+        }
+        return false;
+    }
 
-		return image;
-	}
-	
-	/**
-	 * Constructs a new PrinterJob, allows the user to select which printer
-	 * to print to, And then prints the node.
-	 */
-	public void print() {
-		PrinterJob printJob = PrinterJob.getPrinterJob();
-		PageFormat pageFormat = printJob.defaultPage();
-		Book book = new Book();
-		book.append(this, pageFormat);
-		printJob.setPageable(book);
+    /**
+     * Return true if this node is a descendent of the parameter node.
+     * 
+     * @param node a possible ancestor node
+     * @return true if this nodes descends from the given node
+     */
+    public boolean isDescendentOf(PNode node) {
+        PNode p = parent;
+        while (p != null) {
+            if (p == node)
+                return true;
+            p = p.parent;
+        }
+        return false;
+    }
 
-		if (printJob.printDialog()) {
-			try {
-				printJob.print();
-			} catch (Exception e) {
-				System.out.println("Error Printing");
-				e.printStackTrace();
-			}
-		}
-	}
+    /**
+     * Return true if this node descends from the root.
+     */
+    public boolean isDescendentOfRoot() {
+        return getRoot() != null;
+    }
 
-	/**
-	 * Prints the node into the given Graphics context using the specified
-	 * format. The zero based index of the requested page is specified by
-	 * pageIndex. If the requested page does not exist then this method returns
-	 * NO_SUCH_PAGE; otherwise PAGE_EXISTS is returned. If the printable object
-	 * aborts the print job then it throws a PrinterException.
-	 * 
-	 * @param graphics    the context into which the node is drawn
-	 * @param pageFormat  the size and orientation of the page
-	 * @param pageIndex   the zero based index of the page to be drawn
-	 */
-	public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) {
-		if (pageIndex != 0) {
-			return NO_SUCH_PAGE;
-		}
+    /**
+     * Change the order of this node in its parent's children list so that it
+     * will draw in back of all of its other sibling nodes.
+     */
+    public void moveToBack() {
+        PNode p = parent;
+        if (p != null) {
+            p.removeChild(this);
+            p.addChild(0, this);
+        }
+    }
 
-		Graphics2D g2 = (Graphics2D)graphics;
-		PBounds imageBounds = getFullBounds();
+    /**
+     * Change the order of this node in its parent's children list so that it
+     * will draw in front of all of its other sibling nodes.
+     */
+    public void moveInBackOf(PNode sibling) {
+        PNode p = parent;
+        if (p != null && p == sibling.getParent()) {
+            p.removeChild(this);
+            int index = p.indexOfChild(sibling);
+            p.addChild(index, this);
+        }
+    }
 
-		imageBounds.expandNearestIntegerDimensions();
-		
-		g2.setClip(0, 0, (int)pageFormat.getWidth(), (int)pageFormat.getHeight());
-		g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
+    /**
+     * Change the order of this node in its parent's children list so that it
+     * will draw after the given sibling node.
+     */
+    public void moveToFront() {
+        PNode p = parent;
+        if (p != null) {
+            p.removeChild(this);
+            p.addChild(this);
+        }
+    }
 
-		// scale the graphics so node's full bounds fit in the imageable bounds.
-		double scale = pageFormat.getImageableWidth() / imageBounds.getWidth();
-		if (pageFormat.getImageableHeight() / imageBounds.getHeight() < scale) {
-			scale = pageFormat.getImageableHeight() / imageBounds.getHeight();
-		}
-		
-		g2.scale(scale, scale);
-		g2.translate(-imageBounds.x, -imageBounds.y);
-		
-		PPaintContext pc = new PPaintContext(g2);
-		pc.setRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
-		fullPaint(pc);
+    /**
+     * Change the order of this node in its parent's children list so that it
+     * will draw before the given sibling node.
+     */
+    public void moveInFrontOf(PNode sibling) {
+        PNode p = parent;
+        if (p != null && p == sibling.getParent()) {
+            p.removeChild(this);
+            int index = p.indexOfChild(sibling);
+            p.addChild(index + 1, this);
+        }
+    }
 
-		return PAGE_EXISTS;
-	}	
-	
-	//****************************************************************
-	// Picking - Methods for picking this node and its children.
-	// 
-	// Picking is used to determine the node that intersects a point or 
-	// rectangle on the screen. It is most frequently used by the 
-	// PInputManager to determine the node that the cursor is over.
-	// 
-	// The intersects() method is used to determine if a node has
-	// been picked or not. The default implementation just test to see
-	// if the pick bounds intersects the bounds of the node. Subclasses
-	// whose geometry (a circle for example) does not match up exactly with
-	// the bounds should override the intersects() method.
-	// 
-	// The default picking behavior is to first try to pick the nodes 
-	// children, and then try to pick the nodes own bounds. If a node
-	// wants specialized picking behavior it can override:
-	// 
-	// pick() - Pick nodes here that should be picked before the nodes
-	// children are picked.
-	// pickAfterChildren() - Pick nodes here that should be picked after the
-	// node's children are picked.
-	// 
-	// Note that fullPick should not normally be overridden.
-	// 
-	// The pickable and childrenPickable flags can be used to make a
-	// node or it children not pickable even if their geometry does 
-	// intersect the pick bounds.
-	//****************************************************************
+    /**
+     * Return the parent of this node. This will be null if this node has not
+     * been added to a parent yet.
+     * 
+     * @return this nodes parent or null
+     */
+    public PNode getParent() {
+        return parent;
+    }
 
-	/**
-	 * Return true if this node is pickable. Only pickable nodes can
-	 * receive input events. Nodes are pickable by default.
-	 * 
-	 * @return true if this node is pickable
-	 */
-	public boolean getPickable() {
-		return pickable;
-	}
+    /**
+     * Set the parent of this node. Note this is set automatically when adding
+     * and removing children.
+     */
+    public void setParent(PNode newParent) {
+        PNode old = parent;
+        parent = newParent;
+        firePropertyChange(PROPERTY_CODE_PARENT, PROPERTY_PARENT, old, parent);
+    }
 
-	/**
-	 * Set the pickable flag for this node. Only pickable nodes can
-	 * receive input events.  Nodes are pickable by default.
-	 * 
-	 * @param isPickable true if this node is pickable
-	 */
-	public void setPickable(boolean isPickable) {
-		if (getPickable() != isPickable) {
-			pickable = isPickable;
-			firePropertyChange(PROPERTY_CODE_PICKABLE, PROPERTY_PICKABLE, null, null);
-		}	
-	}
+    /**
+     * Return the index where the given child is stored.
+     */
+    public int indexOfChild(PNode child) {
+        if (children == null)
+            return -1;
+        return children.indexOf(child);
+    }
 
-	/**
-	 * Return true if the children of this node should be picked. If this flag
-	 * is false then this node will not try to pick its children. Children
-	 * are pickable by default.
-	 * 
-	 * @return true if this node tries to pick its children
-	 */
-	public boolean getChildrenPickable() {
-		return childrenPickable;
-	}
+    /**
+     * Remove the given child from this node's children list. Any subsequent
+     * children are shifted to the left (one is subtracted from their indices).
+     * The removed child's parent is set to null.
+     * 
+     * @param child the child to remove
+     * @return the removed child
+     */
+    public PNode removeChild(PNode child) {
+        return removeChild(indexOfChild(child));
+    }
 
-	/**
-	 * Set the children pickable flag. If this flag is false then this 
-	 * node will not try to pick its children. Children are pickable by
-	 * default.
-	 * 
-	 * @param areChildrenPickable true if this node tries to pick its children
-	 */
-	public void setChildrenPickable(boolean areChildrenPickable) {
-		if (getChildrenPickable() != areChildrenPickable) {
-			childrenPickable = areChildrenPickable;
-			firePropertyChange(PROPERTY_CODE_CHILDREN_PICKABLE, PROPERTY_CHILDREN_PICKABLE, null, null);
-		}	
-	}
+    /**
+     * Remove the child at the specified position of this group node's children.
+     * Any subsequent children are shifted to the left (one is subtracted from
+     * their indices). The removed child's parent is set to null.
+     * 
+     * @param index the index of the child to remove
+     * @return the removed child
+     */
+    public PNode removeChild(int index) {
+        PNode child = (PNode) children.remove(index);
 
-	/**
-	 * Try to pick this node before its children have had a chance to be 
-	 * picked. Nodes that paint on top of their children may want to override 
-	 * this method to  if the pick path intersects that paint.
-	 * 
-	 * @param pickPath the pick path used for the pick operation
-	 * @return true if this node was picked
-	 */
-	protected boolean pick(PPickPath pickPath) {
-		return false;
-	}
-	
-	/**
-	 * Try to pick this node and all of its descendents. Most subclasses should not
-	 * need to override this method. Instead they should override pick or
-	 * pickAfterChildren.
-	 * 
-	 * @param pickPath the pick path to add the node to if its picked
-	 * @return true if this node or one of its descendents was picked.
-	 */ 
-	public boolean fullPick(PPickPath pickPath) {
-		if ((getPickable() || getChildrenPickable()) && fullIntersects(pickPath.getPickBounds())) {
-			pickPath.pushNode(this);
-			pickPath.pushTransform(transform);
-			
-			boolean thisPickable = getPickable() && pickPath.acceptsNode(this);
-				
-			if (thisPickable) {
-				if (pick(pickPath)) {
-					return true;
-				}
-			}
-			
-			if (getChildrenPickable()) {
-				int count = getChildrenCount();
-				for (int i = count - 1; i >= 0; i--) {
-					PNode each = (PNode) children.get(i);
-					if (each.fullPick(pickPath))
-						return true;
-				}				
-			}
+        if (children.size() == 0) {
+            children = null;
+        }
 
-			if (thisPickable) {
-				if (pickAfterChildren(pickPath)) {
-					return true;
-				}
-			}
+        child.repaint();
+        child.setParent(null);
+        invalidateFullBounds();
 
-			pickPath.popTransform(transform);
-			pickPath.popNode(this);
-		}
+        firePropertyChange(PROPERTY_CODE_CHILDREN, PROPERTY_CHILDREN, null, children);
 
-		return false;
-	}
+        return child;
+    }
 
-	public void findIntersectingNodes(Rectangle2D fullBounds, ArrayList results) {
-		if (fullIntersects(fullBounds)) {
-			Rectangle2D localBounds = parentToLocal((Rectangle2D)fullBounds.clone());
+    /**
+     * Remove all the children in the given collection from this node's list of
+     * children. All removed nodes will have their parent set to null.
+     * 
+     * @param childrenNodes the collection of children to remove
+     */
+    public void removeChildren(Collection childrenNodes) {
+        Iterator i = childrenNodes.iterator();
+        while (i.hasNext()) {
+            PNode each = (PNode) i.next();
+            removeChild(each);
+        }
+    }
 
-			if (intersects(localBounds)) {
-				results.add(this);
-			}
+    /**
+     * Remove all the children from this node. Node this method is more
+     * efficient then removing each child individually.
+     */
+    public void removeAllChildren() {
+        if (children != null) {
+            int count = children.size();
+            for (int i = 0; i < count; i++) {
+                PNode each = (PNode) children.get(i);
+                each.setParent(null);
+            }
+            children = null;
+            invalidatePaint();
+            invalidateFullBounds();
 
-			int count = getChildrenCount();
-			for (int i = count - 1; i >= 0; i--) {
-				PNode each = (PNode) children.get(i);
-				each.findIntersectingNodes(localBounds, results);
-			}
-		}
-	}
+            firePropertyChange(PROPERTY_CODE_CHILDREN, PROPERTY_CHILDREN, null, children);
+        }
+    }
 
-	/**
-	 * Try to pick this node after its children have had a chance to be 
-	 * picked. Most subclasses the define a different geometry will need to
-	 * override this method.
-	 * 
-	 * @param pickPath the pick path used for the pick operation
-	 * @return true if this node was picked
-	 */
-	protected boolean pickAfterChildren(PPickPath pickPath) {
-		if (intersects(pickPath.getPickBounds())) {
-			return true;
-		}
-		return false;
-	}
-		
-	//****************************************************************
-	// Structure - Methods for manipulating and traversing the 
-	// parent child relationship
-	// 
-	// Most of these methods won't need to be overridden by subclasses
-	// but you will use them frequently to build up your node structures.
-	//****************************************************************
-	 
-	/**
-	 * Add a node to be a new child of this node. The new node
-	 * is added to the end of the list of this node's children.
-	 * If child was previously a child of another node, it is 
-	 * removed from that first.
-	 * 
-	 * @param child the new child to add to this node
-	 */
-	public void addChild(PNode child) {
-		int insertIndex = getChildrenCount();	
-		if (child.parent == this)
-			insertIndex--;
-		addChild(insertIndex, child);
-	}
+    /**
+     * Delete this node by removing it from its parent's list of children.
+     */
+    public void removeFromParent() {
+        if (parent != null) {
+            parent.removeChild(this);
+        }
+    }
 
-	/**
-	 * Add a node to be a new child of this node at the specified index.
-	 * If child was previously a child of another node, it is removed 
-	 * from that node first.
-	 * 
-	 * @param child the new child to add to this node
-	 */
-	public void addChild(int index, PNode child) {
-		PNode oldParent = child.getParent();
+    /**
+     * Set the parent of this node, and transform the node in such a way that it
+     * doesn't move in global coordinates.
+     * 
+     * @param newParent The new parent of this node.
+     */
+    public void reparent(PNode newParent) {
+        AffineTransform originalTransform = getLocalToGlobalTransform(null);
+        AffineTransform newTransform = newParent.getGlobalToLocalTransform(null);
+        newTransform.concatenate(originalTransform);
 
-		if (oldParent != null) {
-			oldParent.removeChild(child);
-		}
-		
-		child.setParent(this);
-		getChildrenReference().add(index, child);
-		child.invalidatePaint();
-		invalidateFullBounds();
-				
-		firePropertyChange(PROPERTY_CODE_CHILDREN, PROPERTY_CHILDREN, null, children); 	
-	}
+        removeFromParent();
+        setTransform(newTransform);
+        newParent.addChild(this);
+        computeFullBounds(fullBoundsCache);
+    }
 
-	/**
-	 * Add a collection of nodes to be children of this node. If these nodes
-	 * already have parents they will first be removed from those parents.
-	 * 
-	 * @param nodes a collection of nodes to be added to this node
-	 */
-	public void addChildren(Collection nodes) {
-		Iterator i = nodes.iterator();
-		while (i.hasNext()) {
-			PNode each = (PNode) i.next();
-			addChild(each);
-		}
-	}
+    /**
+     * Swaps this node out of the scene graph tree, and replaces it with the
+     * specified replacement node. This node is left dangling, and it is up to
+     * the caller to manage it. The replacement node will be added to this
+     * node's parent in the same position as this was. That is, if this was the
+     * 3rd child of its parent, then after calling replaceWith(), the
+     * replacement node will also be the 3rd child of its parent. If this node
+     * has no parent when replace is called, then nothing will be done at all.
+     * 
+     * @param replacementNode the new node that replaces the current node in the
+     *            scene graph tree.
+     */
+    public void replaceWith(PNode replacementNode) {
+        if (parent != null) {
+            PNode p = this.parent;
+            int index = p.getChildrenReference().indexOf(this);
+            p.removeChild(this);
+            p.addChild(index, replacementNode);
+        }
+    }
 
-	/**
-	 * Return true if this node is an ancestor of the parameter node.
-	 * 
-	 * @param node a possible descendent node
-	 * @return true if this node is an ancestor of the given node
-	 */
-	public boolean isAncestorOf(PNode node) {
-		PNode p = node.parent;
-		while (p != null) {
-			if (p == this) return true;
-			p = p.parent;
-		}
-		return false;
-	}
+    /**
+     * Return the number of children that this node has.
+     * 
+     * @return the number of children
+     */
+    public int getChildrenCount() {
+        if (children == null) {
+            return 0;
+        }
+        return children.size();
+    }
 
+    /**
+     * Return the child node at the specified index.
+     * 
+     * @param index a child index
+     * @return the child node at the specified index
+     */
+    public PNode getChild(int index) {
+        return (PNode) children.get(index);
+    }
 
-	/**
-	 * Return true if this node is a descendent of the parameter node.
-	 * 
-	 * @param node a possible ancestor node
-	 * @return true if this nodes descends from the given node
-	 */
-	public boolean isDescendentOf(PNode node) {
-		PNode p = parent;
-		while (p != null) {
-			if (p == node) return true;
-			p = p.parent;
-		}
-		return false;
-	}
-	
-	/**
-	 * Return true if this node descends from the root.
-	 */
-	public boolean isDescendentOfRoot() {
-		return getRoot() != null;
-	}
+    /**
+     * Return a reference to the list used to manage this node's children. This
+     * list should not be modified.
+     * 
+     * @return reference to the children list
+     */
+    public List getChildrenReference() {
+        if (children == null) {
+            children = new ArrayList();
+        }
+        return children;
+    }
 
-	/**
-	 * Change the order of this node in its parent's children list so that
-	 * it will draw in back of all of its other sibling nodes.
-	 */
-	public void moveToBack() {
-		PNode p = parent;
-		if (p != null) {
-			p.removeChild(this);
-			p.addChild(0, this);
-		}
-	}
+    /**
+     * Return an iterator over this node's direct descendent children.
+     * 
+     * @return iterator over this nodes children
+     */
+    public ListIterator getChildrenIterator() {
+        if (children == null) {
+            return Collections.EMPTY_LIST.listIterator();
+        }
+        return Collections.unmodifiableList(children).listIterator();
+    }
 
-	/**
-	 * Change the order of this node in its parent's children list so that
-	 * it will draw in front of all of its other sibling nodes.
-	 */
-	public void moveInBackOf(PNode sibling) {
-		PNode p = parent;
-		if (p != null && p == sibling.getParent()) {
-			p.removeChild(this);
-			int index = p.indexOfChild(sibling);
-			p.addChild(index, this);
-		}
-	}
-		
-	/**
-	 * Change the order of this node in its parent's children list so that
-	 * it will draw after the given sibling node.
-	 */
-	public void moveToFront() {
-		PNode p = parent;
-		if (p != null) {
-			p.removeChild(this);
-			p.addChild(this);
-		}
-	}
+    /**
+     * Return the root node (instance of PRoot). If this node does not descend
+     * from a PRoot then null will be returned.
+     */
+    public PRoot getRoot() {
+        if (parent != null) {
+            return parent.getRoot();
+        }
+        return null;
+    }
 
-	/**
-	 * Change the order of this node in its parent's children list so that
-	 * it will draw before the given sibling node.
-	 */
-	public void moveInFrontOf(PNode sibling) {
-		PNode p = parent;
-		if (p != null && p == sibling.getParent()) {
-			p.removeChild(this);
-			int index = p.indexOfChild(sibling);
-			p.addChild(index + 1, this);
-		}
-	}
+    /**
+     * Return a collection containing this node and all of its descendent nodes.
+     * 
+     * @return a new collection containing this node and all descendents
+     */
+    public Collection getAllNodes() {
+        return getAllNodes(null, null);
+    }
 
-	/**
-	 * Return the parent of this node. This will be null if this node has not been
-	 * added to a parent yet.
-	 * 
-	 * @return this nodes parent or null
-	 */
-	public PNode getParent() {
-		return parent;
-	}
+    /**
+     * Return a collection containing the subset of this node and all of its
+     * descendent nodes that are accepted by the given node filter. If the
+     * filter is null then all nodes will be accepted. If the results parameter
+     * is not null then it will be used to collect this subset instead of
+     * creating a new collection.
+     * 
+     * @param filter the filter used to determine the subset
+     * @return a collection containing this node and all descendents
+     */
+    public Collection getAllNodes(PNodeFilter filter, Collection results) {
+        if (results == null)
+            results = new ArrayList();
+        if (filter == null || filter.accept(this))
+            results.add(this);
 
-	/**
-	 * Set the parent of this node. Note this is set automatically when adding and
-	 * removing children.
-	 */
-	public void setParent(PNode newParent) {
-		PNode old = parent;
-		parent = newParent;
-		firePropertyChange(PROPERTY_CODE_PARENT, PROPERTY_PARENT, old, parent);		
-	}
-	
-	/**
-	 * Return the index where the given child is stored.
-	 */
-	public int indexOfChild(PNode child) {
-		if (children == null) return -1;
-		return children.indexOf(child);
-	}
-	
-	/**
-	 * Remove the given child from this node's children list. Any 
-	 * subsequent children are shifted to the left (one is subtracted 
-	 * from their indices). The removed child's parent is set to null.
-	 * 
-	 * @param child the child to remove
-	 * @return the removed child
-	 */
-	public PNode removeChild(PNode child) {
-		return removeChild(indexOfChild(child));
-	}
+        if (filter == null || filter.acceptChildrenOf(this)) {
+            int count = getChildrenCount();
+            for (int i = 0; i < count; i++) {
+                PNode each = (PNode) children.get(i);
+                each.getAllNodes(filter, results);
+            }
+        }
 
-	/**
-	 * Remove the child at the specified position of this group node's children.
-	 * Any subsequent children are shifted to the left (one is subtracted from
-	 * their indices).	The removed child's parent is set to null.
-	 * 
-	 * @param index the index of the child to remove
-	 * @return the removed child
-	 */
-	public PNode removeChild(int index) {
-		PNode child = (PNode) children.remove(index);
-		
-		if (children.size() == 0) {
-			children = null;
-		}
-		
-		child.repaint();
-		child.setParent(null);
-		invalidateFullBounds();
+        return results;
+    }
 
-		firePropertyChange(PROPERTY_CODE_CHILDREN, PROPERTY_CHILDREN, null, children);
+    // ****************************************************************
+    // Serialization - Nodes conditionally serialize their parent.
+    // This means that only the parents that were unconditionally
+    // (using writeObject) serialized by someone else will be restored
+    // when the node is unserialized.
+    // ****************************************************************
 
-		return child;
-	}
+    /**
+     * Write this node and all of its descendent nodes to the given outputsteam.
+     * This stream must be an instance of PObjectOutputStream or serialization
+     * will fail. This nodes parent is written out conditionally, that is it
+     * will only be written out if someone else writes it out unconditionally.
+     * 
+     * @param out the output stream to write to, must be an instance of
+     *            PObjectOutputStream
+     */
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+        ((PObjectOutputStream) out).writeConditionalObject(parent);
+    }
 
-	/**
-	 * Remove all the children in the given collection from this node's
-	 * list of children. All removed nodes will have their parent set to
-	 * null.
-	 * 
-	 * @param childrenNodes the collection of children to remove
-	 */
-	public void removeChildren(Collection childrenNodes) {
-		Iterator i = childrenNodes.iterator();
-		while (i.hasNext()) {
-			PNode each = (PNode) i.next();
-			removeChild(each);
-		}		
-	}
-	
-	/**
-	 * Remove all the children from this node. Node this method is more efficient then
-	 * removing each child individually.
-	 */
-	public void removeAllChildren() {
-		if (children != null) {
-			int count = children.size();
-			for (int i = 0; i < count; i++) {
-				PNode each = (PNode) children.get(i);
-				each.setParent(null);
-			}
-			children = null;
-			invalidatePaint();
-			invalidateFullBounds();
-			
-			firePropertyChange(PROPERTY_CODE_CHILDREN, PROPERTY_CHILDREN, null, children); 				
-		}
-	}
+    /**
+     * Read this node and all of its descendents in from the given input stream.
+     * 
+     * @param in the stream to read from
+     */
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        parent = (PNode) in.readObject();
+    }
 
-	/**
-	 * Delete this node by removing it from its parent's list of children.
-	 */
-	public void removeFromParent() {
-		if (parent != null) {
-			parent.removeChild(this);
-		}
-	}
-	
-	/**
-	 * Set the parent of this node, and transform the node in such a way that it
-	 * doesn't move in global coordinates.
-	 * 
-	 * @param newParent The new parent of this node.
-	 */
-	public void reparent(PNode newParent) {
-		AffineTransform originalTransform = getLocalToGlobalTransform(null);
-		AffineTransform newTransform = newParent.getGlobalToLocalTransform(null);
-		newTransform.concatenate(originalTransform);
-		
-		removeFromParent();
-		setTransform(newTransform);
-		newParent.addChild(this);
-		computeFullBounds(fullBoundsCache);
-	}
-	
-	/**
-	 * Swaps this node out of the scene graph tree, and replaces it with the specified
-	 * replacement node.  This node is left dangling, and it is up to the caller to
-	 * manage it.  The replacement node will be added to this node's parent in the same
-	 * position as this was.  That is, if this was the 3rd child of its parent, then
-	 * after calling replaceWith(), the replacement node will also be the 3rd child of its parent.
-	 * If this node has no parent when replace is called, then nothing will be done at all.
-	 *
-	 * @param replacementNode the new node that replaces the current node in the scene graph tree.
-	 */
-	public void replaceWith(PNode replacementNode) {
-		if (parent != null) {
-			PNode p = this.parent;
-			int index = p.getChildrenReference().indexOf(this);
-			p.removeChild(this);
-			p.addChild(index, replacementNode);
-		}
-	}
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
 
-	/**
-	 * Return the number of children that this node has.
-	 * 
-	 * @return the number of children
-	 */
-	public int getChildrenCount() {
-		if (children == null) {
-			return 0;
-		}
-		return children.size();
-	}
-	
-	/**
-	 * Return the child node at the specified index.
-	 * 
-	 * @param index a child index
-	 * @return the child node at the specified index
-	 */
-	public PNode getChild(int index) {
-		return (PNode) children.get(index);
-	}
+    /**
+     * Returns a string representation of this object for debugging purposes.
+     */
+    public String toString() {
+        String result = super.toString().replaceAll(".*\\.", "");
+        return result + "[" + paramString() + "]";
+    }
 
-	/**
-	 * Return a reference to the list used to manage this node's
-	 * children. This list should not be modified.
-	 * 
-	 * @return reference to the children list
-	 */
-	public List getChildrenReference() {
-		if (children == null) {
-			children = new ArrayList();
-		}
-		return children;
-	}
-	
-	/**
-	 * Return an iterator over this node's direct descendent children.
-	 *
-	 * @return iterator over this nodes children
-	 */
-	public ListIterator getChildrenIterator() {
-		if (children == null) {
-			return Collections.EMPTY_LIST.listIterator();
-		}
-		return Collections.unmodifiableList(children).listIterator();
-	}
-	
-	/**
-	 * Return the root node (instance of PRoot). If this node does not
-	 * descend from a PRoot then null will be returned.
-	 */
-	public PRoot getRoot() {
-		if (parent != null) {
-			return parent.getRoot();
-		}
-		return null;
-	}
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
 
-	/**
-	 * Return a collection containing this node and all of its descendent nodes.
-	 * 
-	 * @return a new collection containing this node and all descendents
-	 */
-	public Collection getAllNodes() {
-		return getAllNodes(null, null);
-	}
-	
-	/**
-	 * Return a collection containing the subset of this node and all of 
-	 * its descendent nodes that are accepted by the given node filter. If the 
-	 * filter is null then all nodes will be accepted. If the results parameter 
-	 * is not null then it will be used to collect this subset instead of 
-	 * creating a new collection.
-	 * 
-	 * @param filter the filter used to determine the subset
-	 * @return a collection containing this node and all descendents
-	 */
-	public Collection getAllNodes(PNodeFilter filter, Collection results) {
-		if (results == null) results = new ArrayList();
-		if (filter == null || filter.accept(this)) results.add(this);
-		
-		if (filter == null || filter.acceptChildrenOf(this)) {
-			int count = getChildrenCount();
-			for (int i = 0; i < count; i++) {
-				PNode each = (PNode) children.get(i);
-				each.getAllNodes(filter, results);
-			}							
-		}
+        result.append("bounds=" + (bounds == null ? "null" : bounds.toString()));
+        result.append(",fullBounds=" + (fullBoundsCache == null ? "null" : fullBoundsCache.toString()));
+        result.append(",transform=" + (transform == null ? "null" : transform.toString()));
+        result.append(",paint=" + (paint == null ? "null" : paint.toString()));
+        result.append(",transparency=" + transparency);
+        result.append(",childrenCount=" + getChildrenCount());
 
-		return results;
-	}
+        if (fullBoundsInvalid) {
+            result.append(",fullBoundsInvalid");
+        }
 
-	//****************************************************************
-	// Serialization - Nodes conditionally serialize their parent.
-	// This means that only the parents that were unconditionally
-	// (using writeObject) serialized by someone else will be restored
-	// when the node is unserialized.
-	//****************************************************************
-	
-	/**
-	 * Write this node and all of its descendent nodes to the given outputsteam. 
-	 * This stream must be an instance of PObjectOutputStream or serialization 
-	 * will fail. This nodes parent is written out conditionally, that is it will
-	 * only be written out if someone else writes it out unconditionally.
-	 * 
-	 * @param out the output stream to write to, must be an instance of PObjectOutputStream
-	 */
-	private void writeObject(ObjectOutputStream out) throws IOException {
-		out.defaultWriteObject();
-		((PObjectOutputStream)out).writeConditionalObject(parent);
-	}
+        if (pickable) {
+            result.append(",pickable");
+        }
 
-	/**
-	 * Read this node and all of its descendents in from the given input stream.
-	 * 
-	 * @param in the stream to read from
-	 */
-	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
-		in.defaultReadObject();
-		parent = (PNode) in.readObject();
-	}	
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+        if (childrenPickable) {
+            result.append(",childrenPickable");
+        }
 
-	/**
-	 * Returns a string representation of this object for debugging purposes.
-	 */
-	public String toString() {
-		String result = super.toString().replaceAll(".*\\.", "");
-		return result + "[" + paramString() + "]";
-	}	
-	
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
-		
-		result.append("bounds=" + (bounds == null ? "null" : bounds.toString()));
-		result.append(",fullBounds=" + (fullBoundsCache == null ? "null" : fullBoundsCache.toString()));
-		result.append(",transform=" + (transform == null ? "null" : transform.toString()));
-		result.append(",paint=" + (paint == null ? "null" : paint.toString()));
-		result.append(",transparency=" + transparency);
-		result.append(",childrenCount=" + getChildrenCount());
+        if (visible) {
+            result.append(",visible");
+        }
 
-		if (fullBoundsInvalid) {
-			result.append(",fullBoundsInvalid");
-		}
-		
-		if (pickable) {
-			result.append(",pickable");
-		}
-		
-		if (childrenPickable) {
-			result.append(",childrenPickable");
-		}
-		
-		if (visible) {
-			result.append(",visible");
-		}	
-		
-		return result.toString();		
-	}
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/PRoot.java b/core/src/main/java/edu/umd/cs/piccolo/PRoot.java
index dd224db..c380845 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/PRoot.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/PRoot.java
@@ -43,234 +43,241 @@
 import edu.umd.cs.piccolo.util.PNodeFilter;
 
 /**
- * PRoot serves as the top node in Piccolo's runtime structure. 
- * The PRoot responsible for running the main UI loop that processes
- * input from activities and external events.
+ * PRoot serves as the top node in Piccolo's runtime structure. The PRoot
+ * responsible for running the main UI loop that processes input from activities
+ * and external events.
  * processInputs method. When the activity has finished
-	 * running it will automatically get removed.
-	 */
-	public boolean addActivity(PActivity activity) {
-		getActivityScheduler().addActivity(activity);
-		return true;
-	}
-	
-	/**
-	 * Get the activity scheduler associated with this root.
-	 */
-	public PActivityScheduler getActivityScheduler() {
-		return activityScheduler;
-	}
-	
-	/**
-	 * Wait for all scheduled activities to finish before returning from
-	 * this method. This will freeze out user input, and so it is generally
-	 * recommended that you use PActivities.setTriggerTime() to offset activities
-	 * instead of using this method.
-	 */
-	public void waitForActivities() {
-		PNodeFilter cameraWithCanvas = new PNodeFilter() {
-			public boolean accept(PNode aNode) {
-				return (aNode instanceof PCamera) && (((PCamera)aNode).getComponent() != null);
-			}
-			public boolean acceptChildrenOf(PNode aNode) {
-				return true;
-			}
-		};
-		
-		while (activityScheduler.getActivitiesReference().size() > 0) {
-			processInputs();
-			Iterator i = getAllNodes(cameraWithCanvas, null).iterator();
-			while (i.hasNext()) {
-				PCamera each = (PCamera) i.next();
-				each.getComponent().paintImmediately();
-			}
-		}	
-	}
+    /**
+     * Construct a new PRoot(). Note the PCanvas already creates a basic scene
+     * graph for you so often you will not need to construct your own roots.
+     */
+    public PRoot() {
+        super();
+        inputSources = new ArrayList();
+        globalTime = System.currentTimeMillis();
+        activityScheduler = new PActivityScheduler(this);
+    }
 
-	//****************************************************************
-	// Basics
-	//****************************************************************
-	
-	/**
-	 * Return this.
-	 */
-	public PRoot getRoot() {
-		return this;
-	}
-	
-	/**
-	 * Get the default input manager to be used when processing input
-	 * events. PCanvas's use this method when they forward new swing input
-	 * events to the PInputManager.
-	 */
-	public PInputManager getDefaultInputManager() {
-		if (defaultInputManager == null) {
-			defaultInputManager = new PInputManager();
-			addInputSource(defaultInputManager);			
-		}
-		return defaultInputManager;
-	}
-	
-	/**
-	 * Advanced. If you want to add additional input sources to the roots
-	 * UI process you can do that here. You will seldom do this unless you
-	 * are making additions to the piccolo framework.
-	 */
-	public void addInputSource(InputSource inputSource) {
-		inputSources.add(inputSource);
-		firePropertyChange(PROPERTY_CODE_INPUT_SOURCES ,PROPERTY_INPUT_SOURCES, null, inputSources); 			
-	}
-	
-	/**
-	 * Advanced. If you want to remove the default input source from the roots
-	 * UI process you can do that here. You will seldom do this unless you
-	 * are making additions to the piccolo framework.
-	 */
-	public void removeInputSource(InputSource inputSource) {
-		inputSources.remove(inputSource);
-		firePropertyChange(PROPERTY_CODE_INPUT_SOURCES ,PROPERTY_INPUT_SOURCES, null, inputSources); 			
-	}
+    // ****************************************************************
+    // Activities
+    // ****************************************************************
 
-	/**
-	 * Returns a new timer. This method allows subclasses, such as PSWTRoot to
-	 * create custom timers that will be used transparently by the Piccolo
-	 * framework.
-	 */	
-	public Timer createTimer(int delay, ActionListener listener) {
-		return new Timer(delay,listener);
-	}
-	
-	//****************************************************************
-	// UI Loop - Methods for running the main UI loop of Piccolo. 
-	//****************************************************************
-	
-	/**
-	 * Get the global Piccolo time. This is set to System.currentTimeMillis() at
-	 * the beginning of the roots processInputs method. Activities
-	 * should usually use this global time instead of System.
-	 * currentTimeMillis() so that multiple activities will be synchronized.
-	 */
-	public long getGlobalTime() {
-		return globalTime;
-	}
-	
-	/**
-	 * This is the heartbeat of the Piccolo framework. Pending input events
-	 * are processed. Activities are given a chance to run, and the bounds caches
-	 * and any paint damage is validated.
-	 */
-	public void processInputs() {
-		PDebug.startProcessingInput();
-		processingInputs = true;
-		
-		globalTime = System.currentTimeMillis();
-		int count = inputSources == null ? 0: inputSources.size();
-		for (int i = 0; i < count; i++) {
-			InputSource each = (InputSource) inputSources.get(i);
-			each.processInput();
-		}
-		
-		activityScheduler.processActivities(globalTime);
-		validateFullBounds();
-		validateFullPaint();
-		
-		processingInputs = false;
-		PDebug.endProcessingInput();
-	}
+    /**
+     * Add an activity to the activity scheduler associated with this root.
+     * Activities are given a chance to run during each call to the roots
+     * processInputs method. When the activity has finished running
+     * it will automatically get removed.
+     */
+    public boolean addActivity(PActivity activity) {
+        getActivityScheduler().addActivity(activity);
+        return true;
+    }
 
-	public void setFullBoundsInvalid(boolean fullLayoutInvalid) {
-		super.setFullBoundsInvalid(fullLayoutInvalid);
-		scheduleProcessInputsIfNeeded();
-	}
-	
-	public void setChildBoundsInvalid(boolean childLayoutInvalid) {
-		super.setChildBoundsInvalid(childLayoutInvalid);
-		scheduleProcessInputsIfNeeded();
-	}
-	
-	public void setPaintInvalid(boolean paintInvalid) {
-		super.setPaintInvalid(paintInvalid);
-		scheduleProcessInputsIfNeeded();	
-	}
-	
-	public void setChildPaintInvalid(boolean childPaintInvalid) {
-		super.setChildPaintInvalid(childPaintInvalid);
-		scheduleProcessInputsIfNeeded();
-	}
-			
-	public void scheduleProcessInputsIfNeeded() {
-		// The reason for the special case here (when not in the event dispatch thread) is that
-		// the SwingUtilitiles.invokeLater code below only invokes later with respect to the
-		// event dispatch thread, it will invoke concurrently with other threads.
-		if (!SwingUtilities.isEventDispatchThread()) {
-			// Piccolo is not thread safe and should amost always be called from the 
-			// Swing event dispatch thread. It should only reach this point when a new canvas
-			// is being created.
-			return;
-		}
-		
-		PDebug.scheduleProcessInputs();
-		
-		if (!processInputsScheduled && !processingInputs && 
-			(getFullBoundsInvalid() || getChildBoundsInvalid() || getPaintInvalid() || getChildPaintInvalid())) {
-				
-			processInputsScheduled = true;
-			SwingUtilities.invokeLater(new Runnable() {
-				public void run() {
-					processInputs();
-					PRoot.this.processInputsScheduled = false;
-				}
-			});
-		}
-	}
+    /**
+     * Get the activity scheduler associated with this root.
+     */
+    public PActivityScheduler getActivityScheduler() {
+        return activityScheduler;
+    }
+
+    /**
+     * Wait for all scheduled activities to finish before returning from this
+     * method. This will freeze out user input, and so it is generally
+     * recommended that you use PActivities.setTriggerTime() to offset
+     * activities instead of using this method.
+     */
+    public void waitForActivities() {
+        PNodeFilter cameraWithCanvas = new PNodeFilter() {
+            public boolean accept(PNode aNode) {
+                return (aNode instanceof PCamera) && (((PCamera) aNode).getComponent() != null);
+            }
+
+            public boolean acceptChildrenOf(PNode aNode) {
+                return true;
+            }
+        };
+
+        while (activityScheduler.getActivitiesReference().size() > 0) {
+            processInputs();
+            Iterator i = getAllNodes(cameraWithCanvas, null).iterator();
+            while (i.hasNext()) {
+                PCamera each = (PCamera) i.next();
+                each.getComponent().paintImmediately();
+            }
+        }
+    }
+
+    // ****************************************************************
+    // Basics
+    // ****************************************************************
+
+    /**
+     * Return this.
+     */
+    public PRoot getRoot() {
+        return this;
+    }
+
+    /**
+     * Get the default input manager to be used when processing input events.
+     * PCanvas's use this method when they forward new swing input events to the
+     * PInputManager.
+     */
+    public PInputManager getDefaultInputManager() {
+        if (defaultInputManager == null) {
+            defaultInputManager = new PInputManager();
+            addInputSource(defaultInputManager);
+        }
+        return defaultInputManager;
+    }
+
+    /**
+     * Advanced. If you want to add additional input sources to the roots UI
+     * process you can do that here. You will seldom do this unless you are
+     * making additions to the piccolo framework.
+     */
+    public void addInputSource(InputSource inputSource) {
+        inputSources.add(inputSource);
+        firePropertyChange(PROPERTY_CODE_INPUT_SOURCES, PROPERTY_INPUT_SOURCES, null, inputSources);
+    }
+
+    /**
+     * Advanced. If you want to remove the default input source from the roots
+     * UI process you can do that here. You will seldom do this unless you are
+     * making additions to the piccolo framework.
+     */
+    public void removeInputSource(InputSource inputSource) {
+        inputSources.remove(inputSource);
+        firePropertyChange(PROPERTY_CODE_INPUT_SOURCES, PROPERTY_INPUT_SOURCES, null, inputSources);
+    }
+
+    /**
+     * Returns a new timer. This method allows subclasses, such as PSWTRoot to
+     * create custom timers that will be used transparently by the Piccolo
+     * framework.
+     */
+    public Timer createTimer(int delay, ActionListener listener) {
+        return new Timer(delay, listener);
+    }
+
+    // ****************************************************************
+    // UI Loop - Methods for running the main UI loop of Piccolo.
+    // ****************************************************************
+
+    /**
+     * Get the global Piccolo time. This is set to System.currentTimeMillis() at
+     * the beginning of the roots processInputs method. Activities
+     * should usually use this global time instead of System.
+     * currentTimeMillis() so that multiple activities will be synchronized.
+     */
+    public long getGlobalTime() {
+        return globalTime;
+    }
+
+    /**
+     * This is the heartbeat of the Piccolo framework. Pending input events are
+     * processed. Activities are given a chance to run, and the bounds caches
+     * and any paint damage is validated.
+     */
+    public void processInputs() {
+        PDebug.startProcessingInput();
+        processingInputs = true;
+
+        globalTime = System.currentTimeMillis();
+        int count = inputSources == null ? 0 : inputSources.size();
+        for (int i = 0; i < count; i++) {
+            InputSource each = (InputSource) inputSources.get(i);
+            each.processInput();
+        }
+
+        activityScheduler.processActivities(globalTime);
+        validateFullBounds();
+        validateFullPaint();
+
+        processingInputs = false;
+        PDebug.endProcessingInput();
+    }
+
+    public void setFullBoundsInvalid(boolean fullLayoutInvalid) {
+        super.setFullBoundsInvalid(fullLayoutInvalid);
+        scheduleProcessInputsIfNeeded();
+    }
+
+    public void setChildBoundsInvalid(boolean childLayoutInvalid) {
+        super.setChildBoundsInvalid(childLayoutInvalid);
+        scheduleProcessInputsIfNeeded();
+    }
+
+    public void setPaintInvalid(boolean paintInvalid) {
+        super.setPaintInvalid(paintInvalid);
+        scheduleProcessInputsIfNeeded();
+    }
+
+    public void setChildPaintInvalid(boolean childPaintInvalid) {
+        super.setChildPaintInvalid(childPaintInvalid);
+        scheduleProcessInputsIfNeeded();
+    }
+
+    public void scheduleProcessInputsIfNeeded() {
+        /*
+         * The reason for the special case here (when not in the event dispatch
+         * thread) is that the SwingUtilitiles.invokeLater code below only
+         * invokes later with respect to the event dispatch thread, it will
+         * invoke concurrently with other threads.
+         */
+        if (!SwingUtilities.isEventDispatchThread()) {
+            /*
+             * Piccolo is not thread safe and should amost always be called from
+             * the Swing event dispatch thread. It should only reach this point
+             * when a new canvas is being created.
+             */
+            return;
+        }
+
+        PDebug.scheduleProcessInputs();
+
+        if (!processInputsScheduled && !processingInputs
+                && (getFullBoundsInvalid() || getChildBoundsInvalid() || getPaintInvalid() || getChildPaintInvalid())) {
+
+            processInputsScheduled = true;
+            SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                    processInputs();
+                    PRoot.this.processInputsScheduled = false;
+                }
+            });
+        }
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/activities/PActivity.java b/core/src/main/java/edu/umd/cs/piccolo/activities/PActivity.java
index 081fef2..3250bcb 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/activities/PActivity.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/activities/PActivity.java
@@ -32,358 +32,367 @@
 import edu.umd.cs.piccolo.util.PUtil;
 
 /**
- * PActivity controls some time dependent aspect of Piccolo, such
- * as animation. Once created activities must be scheduled with the 
+ * PActivity controls some time dependent aspect of Piccolo, such as
+ * animation. Once created activities must be scheduled with the
  * PActivityScheduler managed by the PRoot to run. They are automatically
  * removed from the scheduler when the animation has finished.
  * null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
+    /**
+     * Set the amount of time that this activity should delay between steps.
+     */
+    public void setStepRate(long aStepRate) {
+        stepRate = aStepRate;
+    }
 
-		result.append("startTime=" + startTime);
-		result.append(",duration=" + duration);
-		result.append(",stepRate=" + stepRate);
-		if (stepping) result.append(",stepping");
-		result.append(",nextStepTime=" + nextStepTime);
+    public long getNextStepTime() {
+        return nextStepTime;
+    }
 
-		return result.toString();
-	}
+    /**
+     * Return the amount of time that this activity should take to complete,
+     * after the startStepping method is called.
+     */
+    public long getDuration() {
+        return duration;
+    }
+
+    /**
+     * Set the amount of time that this activity should take to complete, after
+     * the startStepping method is called.
+     */
+    public void setDuration(long aDuration) {
+        duration = aDuration;
+    }
+
+    public PActivityScheduler getActivityScheduler() {
+        return scheduler;
+    }
+
+    public void setActivityScheduler(PActivityScheduler aScheduler) {
+        scheduler = aScheduler;
+    }
+
+    // ****************************************************************
+    // Stepping
+    // ****************************************************************
+
+    /**
+     * Return true if this activity is stepping.
+     */
+    public boolean isStepping() {
+        return stepping;
+    }
+
+    /**
+     * Return true if this activity is performing an animation. This is used by
+     * the PCanvas to determine if it should set the render quality to
+     * PCanvas.animatingRenderQuality or not for each frame it renders.
+     */
+    protected boolean isAnimation() {
+        return false;
+    }
+
+    /**
+     * This method is called right before an activity is scheduled to start
+     * running. After this method is called step() will be called until the
+     * activity finishes.
+     */
+    protected void activityStarted() {
+        if (delegate != null)
+            delegate.activityStarted(this);
+    }
+
+    /**
+     * This is the method that most activities override to perform their
+     * behavior. It will be called repeatedly when the activity is running.
+     * 
+     * @param elapsedTime the amount of time that has passed relative to the
+     *            activities startTime.
+     */
+    protected void activityStep(long elapsedTime) {
+        if (delegate != null)
+            delegate.activityStepped(this);
+    }
+
+    /**
+     * This method is called after an activity is has finished running and the
+     * activity has been removed from the PActivityScheduler queue.
+     */
+    protected void activityFinished() {
+        if (delegate != null)
+            delegate.activityFinished(this);
+    }
+
+    /**
+     * Get the delegate for this activity. The delegate is notified when the
+     * activity starts and stops stepping.
+     */
+    public PActivityDelegate getDelegate() {
+        return delegate;
+    }
+
+    /**
+     * Set the delegate for this activity. The delegate is notified when the
+     * activity starts and stops stepping.
+     */
+    public void setDelegate(PActivityDelegate delegate) {
+        this.delegate = delegate;
+    }
+
+    // ****************************************************************
+    // Controlling
+    // ****************************************************************
+
+    /**
+     * Schedules this activity to start after the first activity has finished.
+     * Note that no link is created between these activities, if the startTime
+     * or duration of the first activity is later changed this activities start
+     * time will not be updated to reflect that change.
+     */
+    public void startAfter(PActivity first) {
+        setStartTime(first.getStartTime() + first.getDuration());
+    }
+
+    /**
+     * Stop this activity immediately, and remove it from the activity
+     * scheduler. The default termination behavior is call activityFinished if
+     * the activity is currently stepping. Use terminate(terminationBehavior)
+     * use a different termination behavior.
+     */
+    public void terminate() {
+        terminate(TERMINATE_AND_FINISH_IF_STEPPING);
+    }
+
+    /**
+     * Stop this activity immediately, and remove it from the activity
+     * scheduler. The termination behavior determines when and if
+     * activityStarted and activityFinished get called. The possible termination
+     * behaviors are as follow:
+     * 
+     * TERMINATE_WITHOUT_FINISHING - The method activityFinished will never get
+     * called and so the activity will be terminated midway.
+     * TERMINATE_AND_FINISH - The method activityFinished will always get
+     * called. And so the activity will always end in it's completed state. If
+     * the activity has not yet started the method activityStarted will also be
+     * called. TERMINATE_AND_FINISH_IF_STEPPING - The method activityFinished
+     * will only be called if the activity has previously started.
+     */
+    public void terminate(int terminationBehavior) {
+        if (scheduler != null) {
+            scheduler.removeActivity(this);
+        }
+
+        switch (terminationBehavior) {
+            case TERMINATE_WITHOUT_FINISHING:
+                stepping = false;
+                break;
+
+            case TERMINATE_AND_FINISH:
+                if (stepping) {
+                    stepping = false;
+                    activityFinished();
+                }
+                else {
+                    activityStarted();
+                    activityFinished();
+                }
+
+                break;
+
+            case TERMINATE_AND_FINISH_IF_STEPPING:
+                if (stepping) {
+                    stepping = false;
+                    activityFinished();
+                }
+                break;
+        }
+    }
+
+    /**
+     * The activity scheduler calls this method and it is here that the activity
+     * decides if it should do a step or not for the given time.
+     */
+    public long processStep(long currentTime) {
+        // if before start time
+        if (currentTime < startTime) {
+            return startTime - currentTime;
+        }
+
+        // if past stop time
+        if (currentTime > getStopTime()) {
+            if (stepping) {
+                stepping = false;
+                scheduler.removeActivity(this);
+                activityFinished();
+            }
+            else {
+                activityStarted();
+                scheduler.removeActivity(this);
+                activityFinished();
+            }
+            return -1;
+        }
+
+        // else should be stepping
+        if (!stepping) {
+            activityStarted();
+            stepping = true;
+        }
+
+        if (currentTime >= nextStepTime) {
+            activityStep(currentTime - startTime);
+            nextStepTime = currentTime + stepRate;
+        }
+
+        return stepRate;
+    }
+
+    /**
+     * Return the time when this activity should finish running. At this time
+     * (or soon after) the stoppedStepping method will be called
+     */
+    public long getStopTime() {
+        if (duration == -1) {
+            return Long.MAX_VALUE;
+        }
+        return startTime + duration;
+    }
+
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representation of this object for debugging purposes.
+     */
+    public String toString() {
+        String result = super.toString().replaceAll(".*\\.", "");
+        return result + "[" + paramString() + "]";
+    }
+
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append("startTime=" + startTime);
+        result.append(",duration=" + duration);
+        result.append(",stepRate=" + stepRate);
+        if (stepping)
+            result.append(",stepping");
+        result.append(",nextStepTime=" + nextStepTime);
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/activities/PActivityScheduler.java b/core/src/main/java/edu/umd/cs/piccolo/activities/PActivityScheduler.java
index dedcd9b..ff615d7 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/activities/PActivityScheduler.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/activities/PActivityScheduler.java
@@ -41,133 +41,136 @@
 
 /**
  * PActivityScheduler is responsible for maintaining a list of
- * activities. It is given a chance to process these activities from 
- * the PRoot's processInputs() method. Most users will not need to use
- * the PActivityScheduler directly, instead you should look at:
+ * activities. It is given a chance to process these activities from the PRoot's
+ * processInputs() method. Most users will not need to use the
+ * PActivityScheduler directly, instead you should look at:
  * 
- * 
+ * 
  * @version 1.0
  * @author Jesse Grosjean
  */
 public class PActivityScheduler {
-		
-	private PRoot root;
-	private List activities;
-	private Timer activityTimer;
-	private boolean activitiesChanged;
-	private boolean animating;	
-	private ArrayList processingActivities;
 
-	public PActivityScheduler(PRoot rootNode) {
-		root = rootNode;
-		activities = new ArrayList();
-		processingActivities = new ArrayList();
-	}
-		
-	public PRoot getRoot() {
-		return root;
-	}
-	
-	public void addActivity(PActivity activity) {
-		addActivity(activity, false);
-	}
+    private PRoot root;
+    private List activities;
+    private Timer activityTimer;
+    private boolean activitiesChanged;
+    private boolean animating;
+    private ArrayList processingActivities;
 
-	/**
-	 * Add this activity to the scheduler. Sometimes it's useful to make sure
-	 * that an activity is run after all other activities have been run. To do
-	 * this set processLast to true when adding the activity.
-	 */
-	public void addActivity(PActivity activity, boolean processLast) {
-		if (activities.contains(activity)) return;
+    public PActivityScheduler(PRoot rootNode) {
+        root = rootNode;
+        activities = new ArrayList();
+        processingActivities = new ArrayList();
+    }
 
-		activitiesChanged = true;
-		
-		if (processLast) {
-			activities.add(0, activity);
-		} else {
-			activities.add(activity);
-		}
+    public PRoot getRoot() {
+        return root;
+    }
 
-		activity.setActivityScheduler(this);
+    public void addActivity(PActivity activity) {
+        addActivity(activity, false);
+    }
 
-		if (!getActivityTimer().isRunning()) {
-			startActivityTimer();
-		}		
-	}
-		
-	public void removeActivity(PActivity activity) {
-		if (!activities.contains(activity)) return;
+    /**
+     * Add this activity to the scheduler. Sometimes it's useful to make sure
+     * that an activity is run after all other activities have been run. To do
+     * this set processLast to true when adding the activity.
+     */
+    public void addActivity(PActivity activity, boolean processLast) {
+        if (activities.contains(activity))
+            return;
 
-		activitiesChanged = true;
-		activities.remove(activity);
+        activitiesChanged = true;
 
-		if (activities.size() == 0) {
-			stopActivityTimer();
-		}					
-	}
+        if (processLast) {
+            activities.add(0, activity);
+        }
+        else {
+            activities.add(activity);
+        }
 
-	public void removeAllActivities() {		
-		activitiesChanged = true;	
-		activities.clear();
-		stopActivityTimer();
-	}
+        activity.setActivityScheduler(this);
 
-	public List getActivitiesReference() {
-		return activities;
-	}
-	
-	/**
-	 * Process all scheduled activities for the given time. Each activity
-	 * is given one "step", equivalent to one frame of animation.
-	 */	
-	public void processActivities(long currentTime) {
-		int size = activities.size();		
-		if (size > 0) {
-			processingActivities.clear();
-			processingActivities.addAll(activities);
-			for (int i = size - 1; i >= 0; i--) {
-				PActivity each = (PActivity) processingActivities.get(i);
-				each.processStep(currentTime);
-			}
-		}		
-	}
-		
-	/**
-	 * Return true if any of the scheduled activities return true to
-	 * the message isAnimation();
-	 */
-	public boolean getAnimating() {
-		if (activitiesChanged) {
-			animating = false;
-			for(int i=0; inull.
-	 *
-	 * @return  a string representation of this object's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
+    public void setRelativeTargetValue(float zeroToOne) {
+        super.setRelativeTargetValue(zeroToOne);
+        float red = (float) (source.getRed() + (zeroToOne * (destination.getRed() - source.getRed())));
+        float green = (float) (source.getGreen() + (zeroToOne * (destination.getGreen() - source.getGreen())));
+        float blue = (float) (source.getBlue() + (zeroToOne * (destination.getBlue() - source.getBlue())));
+        float alpha = (float) (source.getAlpha() + (zeroToOne * (destination.getAlpha() - source.getAlpha())));
+        target.setColor(new Color(red / 255, green / 255, blue / 255, alpha / 255));
+    }
 
-		result.append("source=" + (source == null ? "null" : source.toString()));
-		result.append(",destination=" + (destination == null ? "null" : destination.toString()));
-		result.append(',');
-		result.append(super.paramString());
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
 
-		return result.toString();
-	}
+    /**
+     * Returns a string representing the state of this object. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this object's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append("source=" + (source == null ? "null" : source.toString()));
+        result.append(",destination=" + (destination == null ? "null" : destination.toString()));
+        result.append(',');
+        result.append(super.paramString());
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/activities/PInterpolatingActivity.java b/core/src/main/java/edu/umd/cs/piccolo/activities/PInterpolatingActivity.java
index 3273296..07cfa76 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/activities/PInterpolatingActivity.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/activities/PInterpolatingActivity.java
@@ -41,227 +41,232 @@
  * A loopCount of greater then one will make the activity reschedule itself when
  * it has finished. This makes the activity loop between the two states.
  * null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
+    /**
+     * Return true if the activity is executing its first loop. Subclasses
+     * normally initialize their source state on the first loop.
+     */
+    public boolean getFirstLoop() {
+        return firstLoop;
+    }
 
-		if (slowInSlowOut) {
-			result.append("slowinSlowOut,");			
-		}
-		
-		result.append(super.paramString());
+    /**
+     * Set if the activity is executing its first loop. Subclasses normally
+     * initialize their source state on the first loop. This method will rarely
+     * need to be called, unless your are reusing activities.
+     */
+    public void setFirstLoop(boolean firstLoop) {
+        this.firstLoop = firstLoop;
+    }
 
-		return result.toString();
-	}	
+    public boolean getSlowInSlowOut() {
+        return slowInSlowOut;
+    }
+
+    public void setSlowInSlowOut(boolean isSlowInSlowOut) {
+        slowInSlowOut = isSlowInSlowOut;
+    }
+
+    // ****************************************************************
+    // Stepping - Instead of overriding the step methods subclasses
+    // of this activity will normally override setRelativeTargetValue().
+    // This method will be called for every step of the activity with
+    // a value ranging from 0,0 (for the first step) to 1.0 (for the
+    // final step). See PTransformActivity for an example.
+    // ****************************************************************
+
+    protected void activityStarted() {
+        super.activityStarted();
+        setRelativeTargetValueAdjustingForMode(0);
+    }
+
+    protected void activityStep(long elapsedTime) {
+        super.activityStep(elapsedTime);
+
+        float t = elapsedTime / (float) getDuration();
+
+        t = Math.min(1, t);
+        t = Math.max(0, t);
+
+        if (getSlowInSlowOut()) {
+            t = computeSlowInSlowOut(t);
+        }
+
+        setRelativeTargetValueAdjustingForMode(t);
+    }
+
+    protected void activityFinished() {
+        setRelativeTargetValueAdjustingForMode(1);
+        super.activityFinished();
+
+        PActivityScheduler scheduler = getActivityScheduler();
+        if (loopCount > 1) {
+            if (loopCount != Integer.MAX_VALUE)
+                loopCount--;
+            firstLoop = false;
+            setStartTime(scheduler.getRoot().getGlobalTime());
+            scheduler.addActivity(this);
+        }
+    }
+
+    /**
+     * Stop this activity immediately, and remove it from the activity
+     * scheduler. If this activity is currently running then stoppedStepping
+     * will be called after it has been removed from the activity scheduler.
+     */
+    public void terminate() {
+        loopCount = 0; // set to zero so that we don't reschedule self.
+        super.terminate();
+    }
+
+    /**
+     * Subclasses should override this method and set the value on their target
+     * (the object that they are modifying) accordingly.
+     */
+    public void setRelativeTargetValue(float zeroToOne) {
+    }
+
+    public float computeSlowInSlowOut(float zeroToOne) {
+        if (zeroToOne < 0.5) {
+            return 2.0f * zeroToOne * zeroToOne;
+        }
+        else {
+            float complement = 1.0f - zeroToOne;
+            return 1.0f - (2.0f * complement * complement);
+        }
+    }
+
+    protected void setRelativeTargetValueAdjustingForMode(float zeroToOne) {
+        switch (mode) {
+            case SOURCE_TO_DESTINATION:
+                break;
+
+            case DESTINATION_TO_SOURCE:
+                zeroToOne = 1 - zeroToOne;
+                break;
+
+            case SOURCE_TO_DESTINATION_TO_SOURCE:
+                if (zeroToOne <= 0.5) {
+                    zeroToOne *= 2;
+                }
+                else {
+                    zeroToOne = 1 - ((zeroToOne - 0.5f) * 2);
+                }
+                break;
+        }
+
+        setRelativeTargetValue(zeroToOne);
+    }
+
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        if (slowInSlowOut) {
+            result.append("slowinSlowOut,");
+        }
+
+        result.append(super.paramString());
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/activities/PTransformActivity.java b/core/src/main/java/edu/umd/cs/piccolo/activities/PTransformActivity.java
index c1c6483..d7360ba 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/activities/PTransformActivity.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/activities/PTransformActivity.java
@@ -39,122 +39,126 @@
  * activity in used. The source transform is retrieved from the target just
  * before the animation is scheduled to start.
  * null.
-	 *
-	 * @return  a string representation of this activity's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
+    protected boolean isAnimation() {
+        return true;
+    }
 
-		result.append("source=" + (source == null ? "null" : source.toString()));
-		result.append(",destination=" + (destination == null ? "null" : destination.toString()));
-		result.append(',');
-		result.append(super.paramString());
-		
-		return result.toString();
-	}	
+    /**
+     * Return the final transform that will be set on the transform activities
+     * target when the transform activity stops stepping.
+     */
+    public double[] getDestinationTransform() {
+        return destination;
+    }
+
+    /**
+     * Set the final transform that will be set on the transform activities
+     * target when the transform activity stops stepping.
+     */
+    public void setDestinationTransform(double[] newDestination) {
+        destination = newDestination;
+    }
+
+    protected void activityStarted() {
+        if (getFirstLoop())
+            target.getSourceMatrix(source);
+        super.activityStarted();
+    }
+
+    public void setRelativeTargetValue(float zeroToOne) {
+        super.setRelativeTargetValue(zeroToOne);
+
+        STATIC_TRANSFORM.setTransform(source[0] + (zeroToOne * (destination[0] - source[0])), source[1]
+                + (zeroToOne * (destination[1] - source[1])), source[2] + (zeroToOne * (destination[2] - source[2])),
+                source[3] + (zeroToOne * (destination[3] - source[3])), source[4]
+                        + (zeroToOne * (destination[4] - source[4])), source[5]
+                        + (zeroToOne * (destination[5] - source[5])));
+
+        target.setTransform(STATIC_TRANSFORM);
+    }
+
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representing the state of this activity. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this activity's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append("source=" + (source == null ? "null" : source.toString()));
+        result.append(",destination=" + (destination == null ? "null" : destination.toString()));
+        result.append(',');
+        result.append(super.paramString());
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/event/PBasicInputEventHandler.java b/core/src/main/java/edu/umd/cs/piccolo/event/PBasicInputEventHandler.java
index 2b78473..c026825 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/event/PBasicInputEventHandler.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/event/PBasicInputEventHandler.java
@@ -35,177 +35,179 @@
 import java.awt.event.MouseWheelEvent;
 
 /**
- * PBasicInputEventHandler is the standard class in Piccolo that
- * is used to register for mouse and keyboard events on a PNode. Note the
- * events that you get depends on the node that you have registered with. For
- * example you will only get mouse moved events when the mouse is over the node
- * that you have registered with, not when the mouse is over some other node.
+ * PBasicInputEventHandler is the standard class in Piccolo that is used
+ * to register for mouse and keyboard events on a PNode. Note the events that
+ * you get depends on the node that you have registered with. For example you
+ * will only get mouse moved events when the mouse is over the node that you
+ * have registered with, not when the mouse is over some other node.
  * null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
-		result.append("eventFilter=" + eventFilter == null ? "null" : eventFilter.toString());
-		return result.toString();
-	}	
+    public void mouseDragged(PInputEvent event) {
+    }
+
+    public void mouseEntered(PInputEvent event) {
+    }
+
+    public void mouseExited(PInputEvent event) {
+    }
+
+    public void mouseMoved(PInputEvent event) {
+    }
+
+    public void mouseReleased(PInputEvent event) {
+    }
+
+    public void mouseWheelRotated(PInputEvent event) {
+    }
+
+    public void mouseWheelRotatedByBlock(PInputEvent event) {
+    }
+
+    public void keyboardFocusGained(PInputEvent event) {
+    }
+
+    public void keyboardFocusLost(PInputEvent event) {
+    }
+
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representation of this object for debugging purposes.
+     */
+    public String toString() {
+        String result = super.toString().replaceAll(".*\\.", "");
+        return result + "[" + paramString() + "]";
+    }
+
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+        result.append("eventFilter=" + eventFilter == null ? "null" : eventFilter.toString());
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/event/PDragEventHandler.java b/core/src/main/java/edu/umd/cs/piccolo/event/PDragEventHandler.java
index 5ac264d..a4c5075 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/event/PDragEventHandler.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/event/PDragEventHandler.java
@@ -35,85 +35,87 @@
 import edu.umd.cs.piccolo.util.PDimension;
 
 /**
- * PDragEventHandler is a simple event handler for dragging a
- * node on the canvas.
+ * PDragEventHandler is a simple event handler for dragging a node on the
+ * canvas.
  * null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
+    protected void drag(PInputEvent event) {
+        super.drag(event);
+        PDimension d = event.getDeltaRelativeTo(draggedNode);
+        draggedNode.localToParent(d);
+        draggedNode.offset(d.getWidth(), d.getHeight());
+    }
 
-		result.append("draggedNode=" + draggedNode == null ? "null" : draggedNode.toString());
-		if (moveToFrontOnPress) result.append(",moveToFrontOnPress");
-		result.append(',');
-		result.append(super.paramString());
+    protected void endDrag(PInputEvent event) {
+        super.endDrag(event);
+        draggedNode = null;
+    }
 
-		return result.toString();
-	}	
+    public boolean getMoveToFrontOnPress() {
+        return moveToFrontOnPress;
+    }
+
+    public void setMoveToFrontOnPress(boolean moveToFrontOnPress) {
+        this.moveToFrontOnPress = moveToFrontOnPress;
+    }
+
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append("draggedNode=" + draggedNode == null ? "null" : draggedNode.toString());
+        if (moveToFrontOnPress)
+            result.append(",moveToFrontOnPress");
+        result.append(',');
+        result.append(super.paramString());
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/event/PDragSequenceEventHandler.java b/core/src/main/java/edu/umd/cs/piccolo/event/PDragSequenceEventHandler.java
index 9287d79..70ff749 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/event/PDragSequenceEventHandler.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/event/PDragSequenceEventHandler.java
@@ -36,227 +36,233 @@
 import edu.umd.cs.piccolo.util.PUtil;
 
 /**
- * PDragSequenceEventHandler is designed to support mouse pressed, dragged, and
- * released interaction sequences. Support is also provided for running a continuous
- * activity during the drag sequence. 
+ * PDragSequenceEventHandler is designed to support mouse pressed,
+ * dragged, and released interaction sequences. Support is also provided for
+ * running a continuous activity during the drag sequence.
  * null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
-		
-		result.append("minDragStartDistance=" + minDragStartDistance);
-		result.append(",mousePressedCanvasPoint=" + (mousePressedCanvasPoint == null ? "null" : mousePressedCanvasPoint.toString()));
-		result.append(",sequenceInitiatedButton=" + sequenceInitiatedButton);
-		if (isDragging) result.append(",dragging");
-		result.append(',');
-		result.append(super.paramString());
-		
-		return result.toString();
-	}
+    // ****************************************************************
+    // Drag Activity - Used for scheduling an activity during a drag
+    // sequence. For example zooming and auto panning are implemented
+    // using this.
+    // ****************************************************************
+
+    protected PActivity getDragActivity() {
+        return dragActivity;
+    }
+
+    protected void startDragActivity(PInputEvent aEvent) {
+        dragActivity = new PActivity(-1, PUtil.DEFAULT_ACTIVITY_STEP_RATE);
+        dragActivity.setDelegate(new PActivity.PActivityDelegate() {
+            public void activityStarted(PActivity activity) {
+                dragActivityFirstStep(dragEvent);
+            }
+
+            public void activityStepped(PActivity activity) {
+                dragActivityStep(dragEvent);
+            }
+
+            public void activityFinished(PActivity activity) {
+                dragActivityFinalStep(dragEvent);
+            }
+        });
+
+        aEvent.getCamera().getRoot().addActivity(dragActivity);
+    }
+
+    protected void stopDragActivity(PInputEvent aEvent) {
+        dragActivity.terminate();
+        dragActivity = null;
+    }
+
+    /**
+     * Override this method to get notified when the drag activity starts
+     * stepping.
+     */
+    protected void dragActivityFirstStep(PInputEvent aEvent) {
+    }
+
+    /**
+     * During a drag sequence an activity is scheduled that runs continuously
+     * while the drag sequence is active. This can be used to support some
+     * additional behavior that is not driven directly by mouse events. For
+     * example PZoomEventHandler uses it for zooming and PPanEventHandler uses
+     * it for auto panning.
+     */
+    protected void dragActivityStep(PInputEvent aEvent) {
+    }
+
+    /**
+     * Override this method to get notified when the drag activity stops
+     * stepping.
+     */
+    protected void dragActivityFinalStep(PInputEvent aEvent) {
+    }
+
+    // ****************************************************************
+    // Events - subclasses should not override these methods, instead
+    // override the appropriate drag method.
+    // ****************************************************************
+
+    public void mousePressed(PInputEvent e) {
+        super.mousePressed(e);
+
+        if (sequenceInitiatedButton == MouseEvent.NOBUTTON) {
+            sequenceInitiatedButton = e.getButton();
+        }
+        else {
+            return;
+        }
+
+        getMousePressedCanvasPoint().setLocation(e.getCanvasPosition());
+        if (!isDragging()) {
+            if (shouldStartDragInteraction(e)) {
+                startDrag(e);
+            }
+        }
+    }
+
+    public void mouseDragged(PInputEvent e) {
+        super.mouseDragged(e);
+
+        if (sequenceInitiatedButton != MouseEvent.NOBUTTON) {
+            if (!isDragging()) {
+                if (shouldStartDragInteraction(e)) {
+                    startDrag(e);
+                }
+                return;
+            }
+            drag(e);
+        }
+    }
+
+    public void mouseReleased(PInputEvent e) {
+        super.mouseReleased(e);
+        if (sequenceInitiatedButton == e.getButton()) {
+            if (isDragging())
+                endDrag(e);
+            sequenceInitiatedButton = MouseEvent.NOBUTTON;
+        }
+    }
+
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append("minDragStartDistance=" + minDragStartDistance);
+        result.append(",mousePressedCanvasPoint="
+                + (mousePressedCanvasPoint == null ? "null" : mousePressedCanvasPoint.toString()));
+        result.append(",sequenceInitiatedButton=" + sequenceInitiatedButton);
+        if (isDragging)
+            result.append(",dragging");
+        result.append(',');
+        result.append(super.paramString());
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/event/PInputEvent.java b/core/src/main/java/edu/umd/cs/piccolo/event/PInputEvent.java
index ca2d7db..b08e094 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/event/PInputEvent.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/event/PInputEvent.java
@@ -46,381 +46,384 @@
 import edu.umd.cs.piccolo.util.PPickPath;
 
 /**
- * PInputEvent is used to notify PInputEventListeners of keyboard and mouse
- * input. It has methods for normal event properties such as event modifier keys
- * and event canvas location.
+ * PInputEvent is used to notify PInputEventListeners of keyboard and
+ * mouse input. It has methods for normal event properties such as event
+ * modifier keys and event canvas location.
  * isPopupTrigger should be checked in both
-	 * mousePressed and mouseReleased for proper
-	 * cross-platform functionality.
-	 *
-	 * @return boolean, true if this event triggers a popup menu for this
-	 * platform
-	 * @throws IllegalStateException if this event is not a mouse event
-	 */
-	public boolean isPopupTrigger() {
-		if (isMouseEvent()) {
-			return ((MouseEvent) inputEvent).isPopupTrigger();
-		}
-		throw new IllegalStateException("Can't get clickcount from key event");
-	}
-		
-	//****************************************************************
-	// Coordinate Systems - Methods for getting mouse location data 
-	// These methods are only designed for use with PInputEvents that 
-	// return true to the isMouseEvent method.
-	//****************************************************************
-	
-	/**
-	 * Return the mouse position in PCanvas coordinates.
-	 */
-	public Point2D getCanvasPosition() {
-		return (Point2D) inputManager.getCurrentCanvasPosition().clone();
-	}
+    // ****************************************************************
+    // Accessing Picked Objects - Methods to access the objects associated
+    // with this event.
+    // isPopupTrigger should be checked in both
+     * mousePressed and mouseReleased for proper
+     * cross-platform functionality.
+     * 
+     * @return boolean, true if this event triggers a popup menu for this
+     *         platform
+     * @throws IllegalStateException if this event is not a mouse event
+     */
+    public boolean isPopupTrigger() {
+        if (isMouseEvent()) {
+            return ((MouseEvent) inputEvent).isPopupTrigger();
+        }
+        throw new IllegalStateException("Can't get clickcount from key event");
+    }
+
+    // ****************************************************************
+    // Coordinate Systems - Methods for getting mouse location data
+    // These methods are only designed for use with PInputEvents that
+    // return true to the isMouseEvent method.
+    // ****************************************************************
+
+    /**
+     * Return the mouse position in PCanvas coordinates.
+     */
+    public Point2D getCanvasPosition() {
+        return (Point2D) inputManager.getCurrentCanvasPosition().clone();
+    }
+
+    /**
+     * Return the delta between the last and current mouse position in PCanvas
+     * coordinates.
+     */
+    public PDimension getCanvasDelta() {
+        Point2D last = inputManager.getLastCanvasPosition();
+        Point2D current = inputManager.getCurrentCanvasPosition();
+        return new PDimension(current.getX() - last.getX(), current.getY() - last.getY());
+    }
+
+    /**
+     * Return the mouse position relative to a given node on the pick path.
+     */
+    public Point2D getPositionRelativeTo(PNode nodeOnPath) {
+        Point2D r = getCanvasPosition();
+        return pickPath.canvasToLocal(r, nodeOnPath);
+    }
+
+    /**
+     * Return the delta between the last and current mouse positions relative to
+     * a given node on the pick path.
+     */
+    public PDimension getDeltaRelativeTo(PNode nodeOnPath) {
+        PDimension r = getCanvasDelta();
+        return (PDimension) pickPath.canvasToLocal(r, nodeOnPath);
+    }
+
+    /**
+     * Return the mouse position transformed through the view transform of the
+     * bottom camera.
+     */
+    public Point2D getPosition() {
+        Point2D r = getCanvasPosition();
+        pickPath.canvasToLocal(r, getCamera());
+        return getCamera().localToView(r);
+    }
+
+    /**
+     * Return the delta between the last and current mouse positions transformed
+     * through the view transform of the bottom camera.
+     */
+    public PDimension getDelta() {
+        PDimension r = getCanvasDelta();
+        pickPath.canvasToLocal(r, getCamera());
+        return (PDimension) getCamera().localToView(r);
+    }
+
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representation of this object for debugging purposes.
+     */
+    public String toString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append(super.toString().replaceAll(".*\\.", ""));
+        result.append('[');
+        if (handled) {
+            result.append("handled");
+        }
+        result.append(']');
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/event/PInputEventFilter.java b/core/src/main/java/edu/umd/cs/piccolo/event/PInputEventFilter.java
index ee627c0..f427cac 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/event/PInputEventFilter.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/event/PInputEventFilter.java
@@ -37,319 +37,329 @@
 
 /**
  * PInputEventFilter is a class that filters input events based on the
- * events modifiers and type. Any PBasicInputEventHandler that is associated 
- * with an event filter will only receive events that pass through the filter. 
+ * events modifiers and type. Any PBasicInputEventHandler that is associated
+ * with an event filter will only receive events that pass through the filter.
  * null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
+    protected void pan(PInputEvent e) {
+        PCamera c = e.getCamera();
+        Point2D l = e.getPosition();
 
-		result.append("minAutopanSpeed=" + minAutopanSpeed);
-		result.append(",maxAutopanSpeed=" + maxAutopanSpeed);
-		if (autopan) result.append(",autopan");
-		result.append(',');
-		result.append(super.paramString());
+        if (c.getViewBounds().contains(l)) {
+            PDimension d = e.getDelta();
+            c.translateView(d.getWidth(), d.getHeight());
+        }
+    }
 
-		return result.toString();
-	}	
+    // ****************************************************************
+    // Auto Pan
+    // ****************************************************************
+
+    public void setAutopan(boolean autopan) {
+        this.autopan = autopan;
+    }
+
+    public boolean getAutopan() {
+        return autopan;
+    }
+
+    /**
+     * Set the minAutoPan speed in pixels per second.
+     * 
+     * @param minAutopanSpeed
+     */
+    public void setMinAutopanSpeed(double minAutopanSpeed) {
+        this.minAutopanSpeed = minAutopanSpeed;
+    }
+
+    /**
+     * Set the maxAutoPan speed in pixes per second.
+     * 
+     * @param maxAutopanSpeed
+     */
+    public void setMaxAutopanSpeed(double maxAutopanSpeed) {
+        this.maxAutopanSpeed = maxAutopanSpeed;
+    }
+
+    /**
+     * Do auto panning even when the mouse is not moving.
+     */
+    protected void dragActivityStep(PInputEvent aEvent) {
+        if (!autopan)
+            return;
+
+        PCamera c = aEvent.getCamera();
+        PBounds b = c.getBoundsReference();
+        Point2D l = aEvent.getPositionRelativeTo(c);
+        int outcode = b.outcode(l);
+        PDimension delta = new PDimension();
+
+        if ((outcode & Rectangle.OUT_TOP) != 0) {
+            delta.height = validatePanningSpeed(-1.0 - (0.5 * Math.abs(l.getY() - b.getY())));
+        }
+        else if ((outcode & Rectangle.OUT_BOTTOM) != 0) {
+            delta.height = validatePanningSpeed(1.0 + (0.5 * Math.abs(l.getY() - (b.getY() + b.getHeight()))));
+        }
+
+        if ((outcode & Rectangle.OUT_RIGHT) != 0) {
+            delta.width = validatePanningSpeed(1.0 + (0.5 * Math.abs(l.getX() - (b.getX() + b.getWidth()))));
+        }
+        else if ((outcode & Rectangle.OUT_LEFT) != 0) {
+            delta.width = validatePanningSpeed(-1.0 - (0.5 * Math.abs(l.getX() - b.getX())));
+        }
+
+        c.localToView(delta);
+
+        if (delta.width != 0 || delta.height != 0) {
+            c.translateView(delta.width, delta.height);
+        }
+    }
+
+    protected double validatePanningSpeed(double delta) {
+        double minDelta = minAutopanSpeed / (1000 / getDragActivity().getStepRate());
+        double maxDelta = maxAutopanSpeed / (1000 / getDragActivity().getStepRate());
+
+        boolean deltaNegative = delta < 0;
+        delta = Math.abs(delta);
+        if (delta < minDelta)
+            delta = minDelta;
+        if (delta > maxDelta)
+            delta = maxDelta;
+        if (deltaNegative)
+            delta = -delta;
+        return delta;
+    }
+
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append("minAutopanSpeed=" + minAutopanSpeed);
+        result.append(",maxAutopanSpeed=" + maxAutopanSpeed);
+        if (autopan)
+            result.append(",autopan");
+        result.append(',');
+        result.append(super.paramString());
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/event/PZoomEventHandler.java b/core/src/main/java/edu/umd/cs/piccolo/event/PZoomEventHandler.java
index 14aab24..5fe3163 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/event/PZoomEventHandler.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/event/PZoomEventHandler.java
@@ -35,18 +35,17 @@
 import edu.umd.cs.piccolo.PCamera;
 
 /**
- * ZoomEventhandler provides event handlers for basic zooming
- * of the canvas view with the right (third) button.  The interaction is that
- * the initial mouse press defines the zoom anchor point, and then
- * moving the mouse to the right zooms with a speed proportional
- * to the amount the mouse is moved to the right of the anchor point.
- * Similarly, if the mouse is moved to the left, the the view is
- * zoomed out.
+ * ZoomEventhandler provides event handlers for basic zooming of the
+ * canvas view with the right (third) button. The interaction is that the
+ * initial mouse press defines the zoom anchor point, and then moving the mouse
+ * to the right zooms with a speed proportional to the amount the mouse is moved
+ * to the right of the anchor point. Similarly, if the mouse is moved to the
+ * left, the the view is zoomed out.
  * 
  * 
  * 
@@ -55,109 +54,115 @@
  * 
  * minScale is larger than
-	 * the current scale.
-	 * @param minScale the minimum scale, must not be negative.
-	 */
-	public void setMinScale(double minScale) {
-		this.minScale = minScale;
-	}
+    /**
+     * Returns the minimum view magnification factor that this event handler is
+     * bound by. The default is 0.
+     * 
+     * @return the minimum camera view scale
+     */
+    public double getMinScale() {
+        return minScale;
+    }
 
-	/**
-	 * Returns the maximum view magnification factor that this event handler is bound by.
-	 * The default is Double.MAX_VALUE.
-	 * @return the maximum camera view scale
-	 */
-	public double getMaxScale() {
-		return maxScale;
-	}
+    /**
+     * Sets the minimum view magnification factor that this event handler is
+     * bound by. The camera is left at its current scale even if
+     * minScale is larger than the current scale.
+     * 
+     * @param minScale the minimum scale, must not be negative.
+     */
+    public void setMinScale(double minScale) {
+        this.minScale = minScale;
+    }
 
-	/**
-	 * Sets the maximum view magnification factor that this event handler is bound by.
-	 * The camera is left at its current scale even if maxScale is smaller than
-	 * the current scale. Use Double.MAX_VALUE to specify the largest possible scale. 
-	 * @param maxScale the maximum scale, must not be negative.
-	 */
-	public void setMaxScale(double maxScale) {
-		this.maxScale = maxScale;
-	}
+    /**
+     * Returns the maximum view magnification factor that this event handler is
+     * bound by. The default is Double.MAX_VALUE.
+     * 
+     * @return the maximum camera view scale
+     */
+    public double getMaxScale() {
+        return maxScale;
+    }
 
-	protected void dragActivityFirstStep(PInputEvent aEvent) {
-		viewZoomPoint = aEvent.getPosition();
-		super.dragActivityFirstStep(aEvent);
-	}
+    /**
+     * Sets the maximum view magnification factor that this event handler is
+     * bound by. The camera is left at its current scale even if
+     * maxScale is smaller than the current scale. Use
+     * Double.MAX_VALUE to specify the largest possible scale.
+     * 
+     * @param maxScale the maximum scale, must not be negative.
+     */
+    public void setMaxScale(double maxScale) {
+        this.maxScale = maxScale;
+    }
 
-	protected void dragActivityStep(PInputEvent aEvent) {
-		PCamera camera = aEvent.getCamera();
-		double dx = aEvent.getCanvasPosition().getX() - getMousePressedCanvasPoint().getX();
-		double scaleDelta = (1.0 + (0.001 * dx));
+    protected void dragActivityFirstStep(PInputEvent aEvent) {
+        viewZoomPoint = aEvent.getPosition();
+        super.dragActivityFirstStep(aEvent);
+    }
 
-		double currentScale = camera.getViewScale();
-		double newScale = currentScale * scaleDelta;
+    protected void dragActivityStep(PInputEvent aEvent) {
+        PCamera camera = aEvent.getCamera();
+        double dx = aEvent.getCanvasPosition().getX() - getMousePressedCanvasPoint().getX();
+        double scaleDelta = (1.0 + (0.001 * dx));
 
-		if (newScale < minScale) {
-			scaleDelta = minScale / currentScale;
-		}
-		if ((maxScale > 0) && (newScale > maxScale)) {
-			scaleDelta = maxScale / currentScale;
-		}
+        double currentScale = camera.getViewScale();
+        double newScale = currentScale * scaleDelta;
 
-		camera.scaleViewAboutPoint(scaleDelta, viewZoomPoint.getX(), viewZoomPoint.getY());
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+        if (newScale < minScale) {
+            scaleDelta = minScale / currentScale;
+        }
+        if ((maxScale > 0) && (newScale > maxScale)) {
+            scaleDelta = maxScale / currentScale;
+        }
 
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
+        camera.scaleViewAboutPoint(scaleDelta, viewZoomPoint.getX(), viewZoomPoint.getY());
+    }
 
-		result.append("minScale=" + minScale);
-		result.append(",maxScale=" + maxScale);
-		result.append(",viewZoomPoint=" + (viewZoomPoint == null ? "null" : viewZoomPoint.toString()));
-		result.append(',');
-		result.append(super.paramString());
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
 
-		return result.toString();
-	}	
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append("minScale=" + minScale);
+        result.append(",maxScale=" + maxScale);
+        result.append(",viewZoomPoint=" + (viewZoomPoint == null ? "null" : viewZoomPoint.toString()));
+        result.append(',');
+        result.append(super.paramString());
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/nodes/PImage.java b/core/src/main/java/edu/umd/cs/piccolo/nodes/PImage.java
index 2e148e0..37759d4 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/nodes/PImage.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/nodes/PImage.java
@@ -52,190 +52,201 @@
  * serialized that image will be converted into a BufferedImage if it is not
  * already one.
  * null, create an
-	 * empty PImage; this behaviour is useful when fetching resources that may
-	 * be missing.
-	 */
-	public PImage(java.net.URL url) {
-		this();
-		if (url != null) setImage(Toolkit.getDefaultToolkit().getImage(url));
-	}	
-	
-	/**
-	 * Returns the image that is shown by this node.
-	 * @return the image that is shown by this node
-	 */ 
-	public Image getImage() {
-		return image;
-	}
+    /**
+     * Construct a new PImage wrapping the given java.awt.Image.
+     */
+    public PImage(Image newImage) {
+        this();
+        setImage(newImage);
+    }
 
-	/**
-	 * Set the image that is wrapped by this PImage node. This method will also
-	 * load the image using a MediaTracker before returning.
-	 */
-	public void setImage(String fileName) {
-		setImage(Toolkit.getDefaultToolkit().getImage(fileName));
-	}
+    /**
+     * Construct a new PImage by loading the given fileName and wrapping the
+     * resulting java.awt.Image.
+     */
+    public PImage(String fileName) {
+        this(Toolkit.getDefaultToolkit().getImage(fileName));
+    }
 
-	/**
-	 * Set the image that is wrapped by this PImage node. This method will also
-	 * load the image using a MediaTracker before returning.
-	 */
-	public void setImage(Image newImage) {
-		Image old = image;
-		
-		if (newImage == null || newImage instanceof BufferedImage) {
-			image = newImage;
-		} else { // else make sure the image is loaded
-			ImageIcon imageLoader = new ImageIcon(newImage);
-			switch (imageLoader.getImageLoadStatus()) {
-				case MediaTracker.LOADING:
-					System.err.println("media tracker still loading image after requested to wait until finished");
+    /**
+     * Construct a new PImage by loading the given url and wrapping the
+     * resulting java.awt.Image. If the url is null, create an
+     * empty PImage; this behaviour is useful when fetching resources that may
+     * be missing.
+     */
+    public PImage(java.net.URL url) {
+        this();
+        if (url != null)
+            setImage(Toolkit.getDefaultToolkit().getImage(url));
+    }
 
-				case MediaTracker.COMPLETE:
-					image = imageLoader.getImage();
-					break;
+    /**
+     * Returns the image that is shown by this node.
+     * 
+     * @return the image that is shown by this node
+     */
+    public Image getImage() {
+        return image;
+    }
 
-				case MediaTracker.ABORTED:
-					System.err.println("media tracker aborted image load");
-					image = null;
-					break;
+    /**
+     * Set the image that is wrapped by this PImage node. This method will also
+     * load the image using a MediaTracker before returning.
+     */
+    public void setImage(String fileName) {
+        setImage(Toolkit.getDefaultToolkit().getImage(fileName));
+    }
 
-				case MediaTracker.ERRORED:
-					System.err.println("media tracker errored image load");
-					image = null;
-					break;
-			}
-		}
-		
-		if (image != null) {					
-			setBounds(0, 0, getImage().getWidth(null), getImage().getHeight(null));
-			invalidatePaint();
-		} else {
-			image = null;
-		}
-		
-		firePropertyChange(PROPERTY_CODE_IMAGE, PROPERTY_IMAGE, old, image);
-	}
+    /**
+     * Set the image that is wrapped by this PImage node. This method will also
+     * load the image using a MediaTracker before returning.
+     */
+    public void setImage(Image newImage) {
+        Image old = image;
 
-	protected void paint(PPaintContext paintContext) {
-		if (getImage() != null) {
-			double iw = image.getWidth(null);
-			double ih = image.getHeight(null);
-			PBounds b = getBoundsReference();
-			Graphics2D g2 = paintContext.getGraphics();
+        if (newImage == null || newImage instanceof BufferedImage) {
+            image = newImage;
+        }
+        else { // else make sure the image is loaded
+            ImageIcon imageLoader = new ImageIcon(newImage);
+            switch (imageLoader.getImageLoadStatus()) {
+                case MediaTracker.LOADING:
+                    System.err.println("media tracker still loading image after requested to wait until finished");
 
-			if (b.x != 0 || b.y != 0 || b.width != iw || b.height != ih) {
-				g2.translate(b.x, b.y);
-				g2.scale(b.width / iw, b.height / ih);
-				g2.drawImage(image, 0, 0, null);
-				g2.scale(iw / b.width, ih / b.height);
-				g2.translate(-b.x, -b.y);
-			} else {
-				g2.drawImage(image, 0, 0, null);
-			}
-		}
-	}
-	
-	//****************************************************************
-	// Serialization
-	//****************************************************************
-	
-	/**
-	 * The java.awt.Image wrapped by this PImage is converted into a BufferedImage
-	 * when serialized.
-	 */
-	private void writeObject(ObjectOutputStream out) throws IOException {
-		out.defaultWriteObject();
-		BufferedImage bufferedImage = toBufferedImage(image, false);
-		if (bufferedImage != null) ImageIO.write(bufferedImage, "png", out);
-	}
+                case MediaTracker.COMPLETE:
+                    image = imageLoader.getImage();
+                    break;
 
-	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
-		in.defaultReadObject();
-		image = ImageIO.read(in);
-	}
-	
-	//****************************************************************
-	// Util
-	//****************************************************************
-	
-	/**
-	 * If alwaysCreateCopy is false then if the image is already a buffered
-	 * image it will not be copied and instead the original image will just be
-	 * returned.
-	 */
-	public static BufferedImage toBufferedImage(Image image, boolean alwaysCreateCopy) {
-		if (image == null) return null;
-		
-		if (!alwaysCreateCopy && image instanceof BufferedImage) {
-			return (BufferedImage) image;
-		} else {
-			GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
-			BufferedImage result = graphicsConfiguration.createCompatibleImage(image.getWidth(null), image.getHeight(null));
-			Graphics2D g2 = result.createGraphics();
-			g2.drawImage(image, 0, 0, null);
-			g2.dispose();
-			return result;
-		}
-	}
-	
-	//****************************************************************
-	// Debugging - methods for debugging
-	//****************************************************************
+                case MediaTracker.ABORTED:
+                    System.err.println("media tracker aborted image load");
+                    image = null;
+                    break;
 
-	/**
-	 * Returns a string representing the state of this node. This method is
-	 * intended to be used only for debugging purposes, and the content and
-	 * format of the returned string may vary between implementations. The
-	 * returned string may be empty but may not be null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
+                case MediaTracker.ERRORED:
+                    System.err.println("media tracker errored image load");
+                    image = null;
+                    break;
+            }
+        }
 
-		result.append("image=" + (image == null ? "null" : image.toString()));
-		result.append(',');
-		result.append(super.paramString());
+        if (image != null) {
+            setBounds(0, 0, getImage().getWidth(null), getImage().getHeight(null));
+            invalidatePaint();
+        }
+        else {
+            image = null;
+        }
 
-		return result.toString();
-	}
+        firePropertyChange(PROPERTY_CODE_IMAGE, PROPERTY_IMAGE, old, image);
+    }
+
+    protected void paint(PPaintContext paintContext) {
+        if (getImage() != null) {
+            double iw = image.getWidth(null);
+            double ih = image.getHeight(null);
+            PBounds b = getBoundsReference();
+            Graphics2D g2 = paintContext.getGraphics();
+
+            if (b.x != 0 || b.y != 0 || b.width != iw || b.height != ih) {
+                g2.translate(b.x, b.y);
+                g2.scale(b.width / iw, b.height / ih);
+                g2.drawImage(image, 0, 0, null);
+                g2.scale(iw / b.width, ih / b.height);
+                g2.translate(-b.x, -b.y);
+            }
+            else {
+                g2.drawImage(image, 0, 0, null);
+            }
+        }
+    }
+
+    // ****************************************************************
+    // Serialization
+    // ****************************************************************
+
+    /**
+     * The java.awt.Image wrapped by this PImage is converted into a
+     * BufferedImage when serialized.
+     */
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+        BufferedImage bufferedImage = toBufferedImage(image, false);
+        if (bufferedImage != null)
+            ImageIO.write(bufferedImage, "png", out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        image = ImageIO.read(in);
+    }
+
+    // ****************************************************************
+    // Util
+    // ****************************************************************
+
+    /**
+     * If alwaysCreateCopy is false then if the image is already a buffered
+     * image it will not be copied and instead the original image will just be
+     * returned.
+     */
+    public static BufferedImage toBufferedImage(Image image, boolean alwaysCreateCopy) {
+        if (image == null)
+            return null;
+
+        if (!alwaysCreateCopy && image instanceof BufferedImage) {
+            return (BufferedImage) image;
+        }
+        else {
+            GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment()
+                    .getDefaultScreenDevice().getDefaultConfiguration();
+            BufferedImage result = graphicsConfiguration.createCompatibleImage(image.getWidth(null), image
+                    .getHeight(null));
+            Graphics2D g2 = result.createGraphics();
+            g2.drawImage(image, 0, 0, null);
+            g2.dispose();
+            return result;
+        }
+    }
+
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append("image=" + (image == null ? "null" : image.toString()));
+        result.append(',');
+        result.append(super.paramString());
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/nodes/PPath.java b/core/src/main/java/edu/umd/cs/piccolo/nodes/PPath.java
index 898aded..22848bd 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/nodes/PPath.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/nodes/PPath.java
@@ -50,392 +50,399 @@
 import edu.umd.cs.piccolo.util.PUtil;
 
 /**
- * PPath is a wrapper around a java.awt.geom.GeneralPath. The
- * setBounds method works by scaling the path to fit into the specified
- * bounds. This normally works well, but if the specified base bounds
- * get too small then it is impossible to expand the path shape again since
- * all its numbers have tended to zero, so application code may need to take
- * this into consideration. 
+ * PPath is a wrapper around a java.awt.geom.GeneralPath. The setBounds
+ * method works by scaling the path to fit into the specified bounds. This
+ * normally works well, but if the specified base bounds get too small then it
+ * is impossible to expand the path shape again since all its numbers have
+ * tended to zero, so application code may need to take this into consideration.
  * startResizeBounds before
- * starting an interaction that may make the bounds very small, and calling 
- * endResizeBounds when this interaction is finished. When this is done
- * PPath will use a copy of the original path to do the resizing so the numbers
- * in the path wont loose resolution.
+ * One option that applications have is to call startResizeBounds
+ * before starting an interaction that may make the bounds very small, and
+ * calling endResizeBounds when this interaction is finished. When
+ * this is done PPath will use a copy of the original path to do the resizing so
+ * the numbers in the path wont loose resolution.
  * null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
+    public void endResizeBounds() {
+        resizePath = null;
+    }
 
-		result.append("path=" + (path == null ? "null" : path.toString()));
-		result.append(",stroke=" + (stroke == null ? "null" : stroke.toString()));
-		result.append(",strokePaint=" + (strokePaint == null ? "null" : strokePaint.toString()));
-		result.append(',');
-		result.append(super.paramString());
+    /**
+     * Set the bounds of this path. This method works by scaling the path to fit
+     * into the specified bounds. This normally works well, but if the specified
+     * base bounds get too small then it is impossible to expand the path shape
+     * again since all its numbers have tended to zero, so application code may
+     * need to take this into consideration.
+     */
+    protected void internalUpdateBounds(double x, double y, double width, double height) {
+        if (updatingBoundsFromPath)
+            return;
+        if (path == null)
+            return;
 
-		return result.toString();
-	}	
+        if (resizePath != null) {
+            path.reset();
+            path.append(resizePath, false);
+        }
+
+        Rectangle2D pathBounds = path.getBounds2D();
+        Rectangle2D pathStrokeBounds = getPathBoundsWithStroke();
+        double strokeOutset = Math.max(pathStrokeBounds.getWidth() - pathBounds.getWidth(), pathStrokeBounds
+                .getHeight()
+                - pathBounds.getHeight());
+
+        x += strokeOutset / 2;
+        y += strokeOutset / 2;
+        width -= strokeOutset;
+        height -= strokeOutset;
+
+        double scaleX = (width == 0 || pathBounds.getWidth() == 0) ? 1 : width / pathBounds.getWidth();
+        double scaleY = (height == 0 || pathBounds.getHeight() == 0) ? 1 : height / pathBounds.getHeight();
+
+        TEMP_TRANSFORM.setToIdentity();
+        TEMP_TRANSFORM.translate(x, y);
+        TEMP_TRANSFORM.scale(scaleX, scaleY);
+        TEMP_TRANSFORM.translate(-pathBounds.getX(), -pathBounds.getY());
+
+        path.transform(TEMP_TRANSFORM);
+    }
+
+    public boolean intersects(Rectangle2D aBounds) {
+        if (super.intersects(aBounds)) {
+            if (getPaint() != null && path.intersects(aBounds)) {
+                return true;
+            }
+            else if (stroke != null && strokePaint != null) {
+                return stroke.createStrokedShape(path).intersects(aBounds);
+            }
+        }
+        return false;
+    }
+
+    public Rectangle2D getPathBoundsWithStroke() {
+        if (stroke != null) {
+            return stroke.createStrokedShape(path).getBounds2D();
+        }
+        else {
+            return path.getBounds2D();
+        }
+    }
+
+    public void updateBoundsFromPath() {
+        updatingBoundsFromPath = true;
+        if (path == null) {
+            resetBounds();
+        }
+        else {
+            Rectangle2D b = getPathBoundsWithStroke();
+            setBounds(b.getX(), b.getY(), b.getWidth(), b.getHeight());
+        }
+        updatingBoundsFromPath = false;
+    }
+
+    // ****************************************************************
+    // Painting
+    // ****************************************************************
+
+    protected void paint(PPaintContext paintContext) {
+        Paint p = getPaint();
+        Graphics2D g2 = paintContext.getGraphics();
+
+        if (p != null) {
+            g2.setPaint(p);
+            g2.fill(path);
+        }
+
+        if (stroke != null && strokePaint != null) {
+            g2.setPaint(strokePaint);
+            g2.setStroke(stroke);
+            g2.draw(path);
+        }
+    }
+
+    // ****************************************************************
+    // Path Support set java.awt.GeneralPath documentation for more
+    // information on using these methods.
+    // ****************************************************************
+
+    public GeneralPath getPathReference() {
+        return path;
+    }
+
+    public void moveTo(float x, float y) {
+        path.moveTo(x, y);
+        firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
+        updateBoundsFromPath();
+        invalidatePaint();
+    }
+
+    public void lineTo(float x, float y) {
+        path.lineTo(x, y);
+        firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
+        updateBoundsFromPath();
+        invalidatePaint();
+    }
+
+    public void quadTo(float x1, float y1, float x2, float y2) {
+        path.quadTo(x1, y1, x2, y2);
+        firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
+        updateBoundsFromPath();
+        invalidatePaint();
+    }
+
+    public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) {
+        path.curveTo(x1, y1, x2, y2, x3, y3);
+        firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
+        updateBoundsFromPath();
+        invalidatePaint();
+    }
+
+    public void append(Shape aShape, boolean connect) {
+        path.append(aShape, connect);
+        firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
+        updateBoundsFromPath();
+        invalidatePaint();
+    }
+
+    public void setPathTo(Shape aShape) {
+        path.reset();
+        append(aShape, false);
+    }
+
+    public void setPathToRectangle(float x, float y, float width, float height) {
+        TEMP_RECTANGLE.setFrame(x, y, width, height);
+        setPathTo(TEMP_RECTANGLE);
+    }
+
+    public void setPathToEllipse(float x, float y, float width, float height) {
+        TEMP_ELLIPSE.setFrame(x, y, width, height);
+        setPathTo(TEMP_ELLIPSE);
+    }
+
+    public void setPathToPolyline(Point2D[] points) {
+        path.reset();
+        path.moveTo((float) points[0].getX(), (float) points[0].getY());
+        for (int i = 1; i < points.length; i++) {
+            path.lineTo((float) points[i].getX(), (float) points[i].getY());
+        }
+        firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
+        updateBoundsFromPath();
+        invalidatePaint();
+    }
+
+    public void setPathToPolyline(float[] xp, float[] yp) {
+        path.reset();
+        path.moveTo(xp[0], yp[0]);
+        for (int i = 1; i < xp.length; i++) {
+            path.lineTo(xp[i], yp[i]);
+        }
+        firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
+        updateBoundsFromPath();
+        invalidatePaint();
+    }
+
+    public void closePath() {
+        path.closePath();
+        firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
+        updateBoundsFromPath();
+        invalidatePaint();
+    }
+
+    public void reset() {
+        path.reset();
+        firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path);
+        updateBoundsFromPath();
+        invalidatePaint();
+    }
+
+    // ****************************************************************
+    // Serialization
+    // ****************************************************************
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        out.defaultWriteObject();
+        PUtil.writeStroke(stroke, out);
+        PUtil.writePath(path, out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        stroke = PUtil.readStroke(in);
+        path = PUtil.readPath(in);
+    }
+
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append("path=" + (path == null ? "null" : path.toString()));
+        result.append(",stroke=" + (stroke == null ? "null" : stroke.toString()));
+        result.append(",strokePaint=" + (strokePaint == null ? "null" : strokePaint.toString()));
+        result.append(',');
+        result.append(super.paramString());
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/nodes/PText.java b/core/src/main/java/edu/umd/cs/piccolo/nodes/PText.java
index f9aec3e..7db4759 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/nodes/PText.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/nodes/PText.java
@@ -44,302 +44,311 @@
 import edu.umd.cs.piccolo.util.PPaintContext;
 
 /**
- * PText is a multi-line text node. The text will flow to base
- * on the width of the node's bounds.
+ * PText is a multi-line text node. The text will flow to base on the
+ * width of the node's bounds.
  * null.
-	 *
-	 * @return  a string representation of this node's state
-	 */
-	protected String paramString() {
-		StringBuffer result = new StringBuffer();
+                y += tl.getDescent() + tl.getLeading();
+            }
+        }
+    }
 
-		result.append("text=" + (text == null ? "null" : text));
-		result.append(",font=" + (font == null ? "null" : font.toString()));
-		result.append(',');
-		result.append(super.paramString());
+    protected void internalUpdateBounds(double x, double y, double width, double height) {
+        recomputeLayout();
+    }
 
-		return result.toString();
-	}
+    // ****************************************************************
+    // Debugging - methods for debugging
+    // ****************************************************************
+
+    /**
+     * Returns a string representing the state of this node. This method is
+     * intended to be used only for debugging purposes, and the content and
+     * format of the returned string may vary between implementations. The
+     * returned string may be empty but may not be null.
+     * 
+     * @return a string representation of this node's state
+     */
+    protected String paramString() {
+        StringBuffer result = new StringBuffer();
+
+        result.append("text=" + (text == null ? "null" : text));
+        result.append(",font=" + (font == null ? "null" : font.toString()));
+        result.append(',');
+        result.append(super.paramString());
+
+        return result.toString();
+    }
 }
diff --git a/core/src/main/java/edu/umd/cs/piccolo/util/PAffineTransform.java b/core/src/main/java/edu/umd/cs/piccolo/util/PAffineTransform.java
index 1e67673..3711294 100644
--- a/core/src/main/java/edu/umd/cs/piccolo/util/PAffineTransform.java
+++ b/core/src/main/java/edu/umd/cs/piccolo/util/PAffineTransform.java
@@ -36,294 +36,289 @@
 import java.awt.geom.Rectangle2D;
 
 /**
- * PAffineTransform is a subclass of AffineTransform that has been extended
- * with convenience methods.
+ * PAffineTransform is a subclass of AffineTransform that has been
+ * extended with convenience methods.
  * writeConditionalObject, which only
- * serializes the specified object to the stream if there is a strong reference (if it
- * has been written somewhere else using writeObject()) to that object elsewhere in the 
- * stream.
+/**
+ * PObjectOutputStream is an extension of ObjectOutputStream to handle
+ * optional elements. This is similar to the concept of Java's
+ * "weak references", but applied to object serialization rather than garbage
+ * collection. Here, PObjectOutputStream provides a method,
+ * writeConditionalObject, which only serializes the specified
+ * object to the stream if there is a strong reference (if it has been written
+ * somewhere else using writeObject()) to that object elsewhere in the stream.
  * writeConditionalObject(object, false)
+ * To discover strong references to objects, PObjectOutputStream uses a
+ * two-phase writing process. First, a "discovery" phase is used to find out
+ * what objects are about to be serialized. This works by effectively
+ * serializing the object graph to /dev/null, recording which objects are
+ * unconditionally written using the standard writeObject method. Then, in the
+ * second "write" phase, ObjectOutputStream actually serializes the data to the
+ * output stream. During this phase, calls to writeConditionalObject() will only
+ * write the specified object if the object was found to be serialized during
+ * the discovery stage. If the object was not recorded during the discovery
+ * stage, a an optional null (the default) is unconditionally written in place
+ * of the object. To skip writting out the null use
+ * writeConditionalObject(object, false)
  *