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 @@ - - - - @@ -97,7 +93,9 @@ - + + + @@ -128,7 +126,9 @@ - + + + @@ -159,10 +159,6 @@ - - - - diff --git a/core/src/build/conf/eclipse-formatting-conventions.xml b/core/src/build/conf/eclipse-formatting-conventions.xml new file mode 100644 index 0000000..d7645e8 --- /dev/null +++ b/core/src/build/conf/eclipse-formatting-conventions.xml @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/src/main/java/edu/umd/cs/piccolo/PCamera.java b/core/src/main/java/edu/umd/cs/piccolo/PCamera.java index 48c5fc3..34f8786 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PCamera.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PCamera.java @@ -55,678 +55,688 @@ import edu.umd.cs.piccolo.util.PUtil; /** - * PCamera represents a viewport onto a list of layer nodes. - * Each camera maintains a view transform through which it views these - * layers. Translating and scaling this view transform is how zooming - * and panning are implemented. + * PCamera represents a viewport onto a list of layer nodes. Each camera + * maintains a view transform through which it views these layers. Translating + * and scaling this view transform is how zooming and panning are implemented. *

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

- * 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 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. + *

+ * 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 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. + *

+ * For example, If you have two nodes, A and B, and you call + * + *

+     * 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. - *

- * For example, If you have two nodes, A and B, and you call - *

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

+ * * @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 - * 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. *

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

    - *
  • PNode.addActivity - to schedule a new activity - *
  • PActivity.terminate - to terminate a running activity + *
  • PNode.addActivity - to schedule a new activity + *
  • PActivity.terminate - to terminate a running activity *
  • PRoot.processInputs - already calls processActivities for you. *
+ * * @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; i 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; i < activities.size(); i++) { + PActivity each = (PActivity) activities.get(i); + animating |= each.isAnimation(); + } + activitiesChanged = false; + } + return animating; + } + + protected void startActivityTimer() { + getActivityTimer().start(); + } + + protected void stopActivityTimer() { + getActivityTimer().stop(); + } + + protected Timer getActivityTimer() { + if (activityTimer == null) { + activityTimer = root.createTimer(PUtil.ACTIVITY_SCHEDULER_FRAME_DELAY, new ActionListener() { + public void actionPerformed(ActionEvent e) { + root.processInputs(); + } + }); + } + return activityTimer; + } } - diff --git a/core/src/main/java/edu/umd/cs/piccolo/activities/PColorActivity.java b/core/src/main/java/edu/umd/cs/piccolo/activities/PColorActivity.java index 70d21b8..c062d0f 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/activities/PColorActivity.java +++ b/core/src/main/java/edu/umd/cs/piccolo/activities/PColorActivity.java @@ -35,114 +35,117 @@ * duration of the animation. The source color is retrieved from the target just * before the activity is scheduled to start. *

+ * * @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 null. - * - * @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. *

+ * * @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 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. *

+ * * @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 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. *

+ * * @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 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. *

+ * * @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 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. *

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

- * 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, 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. + //

+ // 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, 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. *

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

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

* *

@@ -55,109 +54,115 @@
  * 
* *

+ * * @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 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. *

+ * * @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 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. *

- * 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. + * 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. *

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

+ * * @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 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. *

+ * * @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, 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. *

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

- * 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); + } }