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)
*