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 af1def3..eb63f44 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PCamera.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PCamera.java @@ -111,7 +111,9 @@ /** Denotes that the view has no constraints. */ public static final int VIEW_CONSTRAINT_NONE = 0; + /** Enforces that the view be able to see all nodes in the scene. */ public static final int VIEW_CONSTRAINT_ALL = 1; + /** Constrains the the view to be centered on the scene's full bounds. */ public static final int VIEW_CONSTRAINT_CENTER = 2; /** 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 34f5706..629be5b 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PCanvas.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PCanvas.java @@ -130,10 +130,10 @@ private int interactingRenderQuality; /** The one and only pan handler. */ - private PPanEventHandler panEventHandler; + private transient PPanEventHandler panEventHandler; /** The one and only ZoomEventHandler. */ - private PZoomEventHandler zoomEventHandler; + private transient PZoomEventHandler zoomEventHandler; private boolean paintingImmediately; @@ -388,11 +388,11 @@ * when it is not interacting or animating. The default value is * PPaintContext. HIGH_QUALITY_RENDERING. * - * @param normalRenderQuality supports PPaintContext.HIGH_QUALITY_RENDERING + * @param defaultRenderQuality supports PPaintContext.HIGH_QUALITY_RENDERING * or PPaintContext.LOW_QUALITY_RENDERING */ - public void setDefaultRenderQuality(final int normalRenderQuality) { - this.normalRenderQuality = normalRenderQuality; + public void setDefaultRenderQuality(final int defaultRenderQuality) { + this.normalRenderQuality = defaultRenderQuality; repaint(); } @@ -490,209 +490,22 @@ */ protected void installInputSources() { if (mouseListener == null) { - mouseListener = new MouseListener() { - /** {@inheritDoc} */ - public void mouseClicked(final MouseEvent e) { - sendInputEventToInputManager(e, MouseEvent.MOUSE_CLICKED); - } - - /** {@inheritDoc} */ - public void mouseEntered(final MouseEvent e) { - MouseEvent simulated = null; - - if (isAnyButtonDown(e)) { - simulated = buildRetypedMouseEvent(e, MouseEvent.MOUSE_DRAGGED); - } - else { - simulated = buildRetypedMouseEvent(e, MouseEvent.MOUSE_MOVED); - } - - sendInputEventToInputManager(e, MouseEvent.MOUSE_ENTERED); - sendInputEventToInputManager(simulated, simulated.getID()); - } - - /** {@inheritDoc} */ - public void mouseExited(final MouseEvent e) { - MouseEvent simulated = null; - - if (isAnyButtonDown(e)) { - simulated = buildRetypedMouseEvent(e, MouseEvent.MOUSE_DRAGGED); - } - else { - simulated = buildRetypedMouseEvent(e, MouseEvent.MOUSE_MOVED); - } - - sendInputEventToInputManager(simulated, simulated.getID()); - sendInputEventToInputManager(e, MouseEvent.MOUSE_EXITED); - } - - /** {@inheritDoc} */ - public void mousePressed(final MouseEvent rawEvent) { - requestFocus(); - - boolean shouldBalanceEvent = false; - - final MouseEvent event = copyButtonsFromModifiers(rawEvent, MouseEvent.MOUSE_PRESSED); - - switch (event.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; - default: - throw new RuntimeException("mousePressed without buttons specified"); - - } - - if (shouldBalanceEvent) { - sendRetypedMouseEventToInputManager(event, MouseEvent.MOUSE_RELEASED); - } - - sendInputEventToInputManager(event, MouseEvent.MOUSE_PRESSED); - } - - /** {@inheritDoc} */ - public void mouseReleased(final MouseEvent rawEvent) { - boolean shouldBalanceEvent = false; - - final MouseEvent event = copyButtonsFromModifiers(rawEvent, MouseEvent.MOUSE_RELEASED); - - switch (event.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; - default: - throw new RuntimeException("mouseReleased without buttons specified"); - } - - if (shouldBalanceEvent) { - sendRetypedMouseEventToInputManager(event, MouseEvent.MOUSE_PRESSED); - } - - sendInputEventToInputManager(event, MouseEvent.MOUSE_RELEASED); - } - - private boolean isAnyButtonDown(final MouseEvent e) { - return (e.getModifiersEx() & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK)) != 0; - } - - private MouseEvent copyButtonsFromModifiers(final MouseEvent rawEvent, final int eventType) { - if (rawEvent.getButton() != MouseEvent.NOBUTTON) { - return rawEvent; - } - - int newButton = 0; - - if (hasButtonModifier(rawEvent, InputEvent.BUTTON1_MASK)) { - newButton = MouseEvent.BUTTON1; - } - else if (hasButtonModifier(rawEvent, InputEvent.BUTTON2_MASK)) { - newButton = MouseEvent.BUTTON2; - } - else if (hasButtonModifier(rawEvent, InputEvent.BUTTON3_MASK)) { - newButton = MouseEvent.BUTTON3; - } - - return buildModifiedMouseEvent(rawEvent, eventType, newButton); - } - - private boolean hasButtonModifier(final MouseEvent event, final int buttonMask) { - return (event.getModifiers() & buttonMask) == buttonMask; - } - - public MouseEvent buildRetypedMouseEvent(final MouseEvent e, final int newType) { - return buildModifiedMouseEvent(e, newType, e.getButton()); - } - - public MouseEvent buildModifiedMouseEvent(final MouseEvent e, final int newType, final int newButton) { - return new MouseEvent((Component) e.getSource(), newType, e.getWhen(), e.getModifiers(), e.getX(), - e.getY(), e.getClickCount(), e.isPopupTrigger(), newButton); - } - - private void sendRetypedMouseEventToInputManager(final MouseEvent e, final int newType) { - final MouseEvent retypedEvent = buildRetypedMouseEvent(e, newType); - sendInputEventToInputManager(retypedEvent, newType); - } - }; + mouseListener = new MouseEventInputSource(); addMouseListener(mouseListener); } if (mouseMotionListener == null) { - mouseMotionListener = new MouseMotionListener() { - /** {@inheritDoc} */ - public void mouseDragged(final MouseEvent e) { - sendInputEventToInputManager(e, MouseEvent.MOUSE_DRAGGED); - } - - /** {@inheritDoc} */ - public void mouseMoved(final MouseEvent e) { - sendInputEventToInputManager(e, MouseEvent.MOUSE_MOVED); - } - }; + mouseMotionListener = new MouseMotionInputSourceListener(); addMouseMotionListener(mouseMotionListener); } if (mouseWheelListener == null) { - mouseWheelListener = new MouseWheelListener() { - /** {@inheritDoc} */ - public void mouseWheelMoved(final MouseWheelEvent e) { - sendInputEventToInputManager(e, e.getScrollType()); - if (!e.isConsumed() && getParent() != null) { - getParent().dispatchEvent(e); - } - } - }; + mouseWheelListener = new MouseWheelInputSourceListener(); addMouseWheelListener(mouseWheelListener); } if (keyEventPostProcessor == null) { - keyEventPostProcessor = new KeyEventPostProcessor() { - /** {@inheritDoc} */ - public boolean postProcessKeyEvent(final KeyEvent keyEvent) { - Component owner = FocusManager.getCurrentManager().getFocusOwner(); - while (owner != null) { - if (owner == PCanvas.this) { - sendInputEventToInputManager(keyEvent, keyEvent.getID()); - return true; - } - owner = owner.getParent(); - } - return false; - } - }; + keyEventPostProcessor = new KeyEventInputSourceListener(); KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(keyEventPostProcessor); } } @@ -885,7 +698,7 @@ getCamera().setBounds(layerBounds); final double clipRatio = clippingRect.getWidth() / clippingRect.getHeight(); - final double nodeRatio = ((double)getWidth()) / ((double)getHeight()); + final double nodeRatio = ((double) getWidth()) / ((double) getHeight()); final double scale; if (nodeRatio <= clipRatio) { scale = clippingRect.getHeight() / getCamera().getHeight(); @@ -902,4 +715,203 @@ getCamera().setBounds(originalCameraBounds); } + + private final class MouseMotionInputSourceListener implements MouseMotionListener { + /** {@inheritDoc} */ + public void mouseDragged(final MouseEvent e) { + sendInputEventToInputManager(e, MouseEvent.MOUSE_DRAGGED); + } + + /** {@inheritDoc} */ + public void mouseMoved(final MouseEvent e) { + sendInputEventToInputManager(e, MouseEvent.MOUSE_MOVED); + } + } + + private final class MouseEventInputSource implements MouseListener { + /** {@inheritDoc} */ + public void mouseClicked(final MouseEvent e) { + sendInputEventToInputManager(e, MouseEvent.MOUSE_CLICKED); + } + + /** {@inheritDoc} */ + public void mouseEntered(final MouseEvent e) { + MouseEvent simulated = null; + + if (isAnyButtonDown(e)) { + simulated = buildRetypedMouseEvent(e, MouseEvent.MOUSE_DRAGGED); + } + else { + simulated = buildRetypedMouseEvent(e, MouseEvent.MOUSE_MOVED); + } + + sendInputEventToInputManager(e, MouseEvent.MOUSE_ENTERED); + sendInputEventToInputManager(simulated, simulated.getID()); + } + + /** {@inheritDoc} */ + public void mouseExited(final MouseEvent e) { + MouseEvent simulated = null; + + if (isAnyButtonDown(e)) { + simulated = buildRetypedMouseEvent(e, MouseEvent.MOUSE_DRAGGED); + } + else { + simulated = buildRetypedMouseEvent(e, MouseEvent.MOUSE_MOVED); + } + + sendInputEventToInputManager(simulated, simulated.getID()); + sendInputEventToInputManager(e, MouseEvent.MOUSE_EXITED); + } + + /** {@inheritDoc} */ + public void mousePressed(final MouseEvent rawEvent) { + requestFocus(); + + boolean shouldBalanceEvent = false; + + final MouseEvent event = copyButtonsFromModifiers(rawEvent, MouseEvent.MOUSE_PRESSED); + + switch (event.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; + default: + throw new RuntimeException("mousePressed without buttons specified"); + + } + + if (shouldBalanceEvent) { + sendRetypedMouseEventToInputManager(event, MouseEvent.MOUSE_RELEASED); + } + + sendInputEventToInputManager(event, MouseEvent.MOUSE_PRESSED); + } + + /** {@inheritDoc} */ + public void mouseReleased(final MouseEvent rawEvent) { + boolean shouldBalanceEvent = false; + + final MouseEvent event = copyButtonsFromModifiers(rawEvent, MouseEvent.MOUSE_RELEASED); + + switch (event.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; + default: + throw new RuntimeException("mouseReleased without buttons specified"); + } + + if (shouldBalanceEvent) { + sendRetypedMouseEventToInputManager(event, MouseEvent.MOUSE_PRESSED); + } + + sendInputEventToInputManager(event, MouseEvent.MOUSE_RELEASED); + } + + private boolean isAnyButtonDown(final MouseEvent e) { + return (e.getModifiersEx() & (InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON2_DOWN_MASK | InputEvent.BUTTON3_DOWN_MASK)) != 0; + } + + private MouseEvent copyButtonsFromModifiers(final MouseEvent rawEvent, final int eventType) { + if (rawEvent.getButton() != MouseEvent.NOBUTTON) { + return rawEvent; + } + + int newButton = 0; + + if (hasButtonModifier(rawEvent, InputEvent.BUTTON1_MASK)) { + newButton = MouseEvent.BUTTON1; + } + else if (hasButtonModifier(rawEvent, InputEvent.BUTTON2_MASK)) { + newButton = MouseEvent.BUTTON2; + } + else if (hasButtonModifier(rawEvent, InputEvent.BUTTON3_MASK)) { + newButton = MouseEvent.BUTTON3; + } + + return buildModifiedMouseEvent(rawEvent, eventType, newButton); + } + + private boolean hasButtonModifier(final MouseEvent event, final int buttonMask) { + return (event.getModifiers() & buttonMask) == buttonMask; + } + + public MouseEvent buildRetypedMouseEvent(final MouseEvent e, final int newType) { + return buildModifiedMouseEvent(e, newType, e.getButton()); + } + + public MouseEvent buildModifiedMouseEvent(final MouseEvent e, final int newType, final int newButton) { + return new MouseEvent((Component) e.getSource(), newType, e.getWhen(), e.getModifiers(), e.getX(), + e.getY(), e.getClickCount(), e.isPopupTrigger(), newButton); + } + + private void sendRetypedMouseEventToInputManager(final MouseEvent e, final int newType) { + final MouseEvent retypedEvent = buildRetypedMouseEvent(e, newType); + sendInputEventToInputManager(retypedEvent, newType); + } + } + + /** + * Class responsible for sending key events to the the InputManager. + */ + private final class KeyEventInputSourceListener implements KeyEventPostProcessor { + /** {@inheritDoc} */ + public boolean postProcessKeyEvent(final KeyEvent keyEvent) { + Component owner = FocusManager.getCurrentManager().getFocusOwner(); + while (owner != null) { + if (owner == PCanvas.this) { + sendInputEventToInputManager(keyEvent, keyEvent.getID()); + return true; + } + owner = owner.getParent(); + } + return false; + } + } + + private final class MouseWheelInputSourceListener implements MouseWheelListener { + /** {@inheritDoc} */ + public void mouseWheelMoved(final MouseWheelEvent e) { + sendInputEventToInputManager(e, e.getScrollType()); + if (!e.isConsumed() && getParent() != null) { + getParent().dispatchEvent(e); + } + } + } + } \ No newline at end of file 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 0741777..c172713 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PInputManager.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PInputManager.java @@ -89,8 +89,7 @@ * Creates a PInputManager and sets positions (last, current) to the origin * (0,0). */ - public PInputManager() { - super(); + public PInputManager() { lastCanvasPosition = new Point2D.Double(); currentCanvasPosition = new Point2D.Double(); } 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 a701c8a..1121896 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PNode.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PNode.java @@ -96,6 +96,14 @@ */ public class PNode implements Cloneable, Serializable, Printable { /** + * The minimum difference in transparency required before the transparency + * is allowed to change. Done for efficiency reasons. I doubt very much that + * the human eye could tell the difference between 0.01 and 0.02 + * transparency. + */ + private static final float TRANSPARENCY_RESOLUTION = 0.01f; + + /** * Allows for future serialization code to understand versioned binary * formats. */ @@ -843,19 +851,8 @@ */ 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(); - } - }; + return new ClientPropertyKeyIterator(enumeration); } // **************************************************************** @@ -2770,7 +2767,7 @@ * transparent, 1f = fully opaque */ public void setTransparency(final float newTransparency) { - if (Math.abs(transparency - newTransparency) > 0.01f) { + if (Math.abs(transparency - newTransparency) > TRANSPARENCY_RESOLUTION) { final float oldTransparency = transparency; transparency = newTransparency; invalidatePaint(); @@ -2901,7 +2898,7 @@ * @return a rendering of this image and its descendants onto the specified * image */ - public Image toImage(final BufferedImage image, final Paint backGroundPaint, int fillStrategy) { + public Image toImage(final BufferedImage image, final Paint backGroundPaint, final int fillStrategy) { final int imageWidth = image.getWidth(); final int imageHeight = image.getHeight(); final Graphics2D g2 = image.createGraphics(); @@ -3698,6 +3695,26 @@ return result; } + private static final class ClientPropertyKeyIterator implements Iterator { + private final Enumeration enumeration; + + private ClientPropertyKeyIterator(final Enumeration enumeration) { + this.enumeration = enumeration; + } + + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + + public Object next() { + return enumeration.nextElement(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + /** * PSceneGraphDelegate is an interface to receive low level node * events. It together with PNode.SCENE_GRAPH_DELEGATE gives Piccolo2d users 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 e9b6009..4fe6bb3 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/PRoot.java +++ b/core/src/main/java/edu/umd/cs/piccolo/PRoot.java @@ -164,15 +164,7 @@ * activities instead of using this method. */ public void waitForActivities() { - final PNodeFilter cameraWithCanvas = new PNodeFilter() { - public boolean accept(final PNode aNode) { - return aNode instanceof PCamera && ((PCamera) aNode).getComponent() != null; - } - - public boolean acceptChildrenOf(final PNode aNode) { - return true; - } - }; + final PNodeFilter cameraWithCanvas = new CameraWithCanvasFilter(); while (activityScheduler.getActivitiesReference().size() > 0) { processInputs(); @@ -395,6 +387,16 @@ } } + private static final class CameraWithCanvasFilter implements PNodeFilter { + public boolean accept(final PNode aNode) { + return aNode instanceof PCamera && ((PCamera) aNode).getComponent() != null; + } + + public boolean acceptChildrenOf(final PNode aNode) { + return true; + } + } + /** * This interfaces is for advanced use only. If you want to implement a * different kind of input framework then Piccolo2D provides you can hook it 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 2c85679..5aa0c77 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 @@ -69,7 +69,7 @@ * @param rootNode root node of all activities to be performed. All nodes * being animated should have this node as an ancestor. */ - public PActivityScheduler(final PRoot rootNode) { + public PActivityScheduler(final PRoot rootNode) { root = rootNode; activities = new ArrayList(); processingActivities = new ArrayList(); 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 86d4667..deca564 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 @@ -57,7 +57,7 @@ * * @param color the color to assign to the target */ - public void setColor(Color color); + void setColor(Color color); /** * This method is called right before the color activity starts. That @@ -65,7 +65,7 @@ * * @return the target's current color. */ - public Color getColor(); + Color getColor(); } /** @@ -84,8 +84,9 @@ /** * Constructs a color activity for the given target that will animate for - * the duration provided at an interval of stepRate from the target's starting color to the destination color. - * + * the duration provided at an interval of stepRate from the target's + * starting color to the destination color. + * * @param duration duration in milliseconds that the animation should last * @param stepRate the time between interpolations * @param aTarget the target onto which the animation is being performed @@ -136,7 +137,7 @@ * Set the final color that will be set on the color activities target when * the activity stops stepping. * - * @param changes this activity's destination color + * @param newDestination to animate towards */ public void setDestinationColor(final Color newDestination) { destination = newDestination; 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 6eb0a92..c129c4b 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 @@ -69,10 +69,28 @@ private int loopCount; private boolean firstLoop; + /** + * Constructs an interpolating activity that will last the duration given + * and will update its target at the given rate. + * + * @param duration duration in milliseconds of the entire activity + * @param stepRate interval in milliseconds between updates to target + */ public PInterpolatingActivity(final long duration, final long stepRate) { this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION); } + /** + * Constructs an interpolating activity that will last the duration given + * and will update its target at the given rate. Once done, it will repeat + * the loopCount times. + * + * @param duration duration in milliseconds of the entire activity + * @param stepRate interval in milliseconds between updates to target + * @param loopCount # of times to repeat this activity. + * @param mode controls the direction of the interpolation (source to destination, + * destination to source, or source to destination back to source) + */ public PInterpolatingActivity(final long duration, final long stepRate, final int loopCount, final int mode) { this(duration, stepRate, System.currentTimeMillis(), loopCount, mode); } @@ -84,7 +102,7 @@ * @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. + * this activity should start. This value can be in the future. * @param loopCount number of times the activity should reschedule itself * @param mode defines how the activity interpolates between states */ @@ -101,13 +119,15 @@ * 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. + * + * @param duration new duration of this activity */ - public void setDuration(final long aDuration) { - if (aDuration <= 0) { + public void setDuration(final long duration) { + if (duration <= 0) { throw new IllegalArgumentException("Duration for PInterpolatingActivity must be greater then 0"); } - super.setDuration(aDuration); + super.setDuration(duration); } // **************************************************************** @@ -115,15 +135,24 @@ // **************************************************************** /** - * Return the mode that defines how the activity interpolates between - * states. + * Return the mode used for interpolation. + * + * Acceptable values are: SOURCE_TO_DESTINATION, DESTINATION_TO_SOURCE and + * SOURCE_TO_DESTINATION_TO_SOURCE + * + * @return current mode of this activity */ public int getMode() { return mode; } /** - * Set the mode that defines how the activity interpolates between states. + * Set the direction in which interpolation is going to occur. + * + * Acceptable values are: SOURCE_TO_DESTINATION, DESTINATION_TO_SOURCE and + * SOURCE_TO_DESTINATION_TO_SOURCE + * + * @param mode the new mode to use when interpolating */ public void setMode(final int mode) { this.mode = mode; @@ -231,8 +260,8 @@ } /** - * Called whenever the activity finishes. Reschedules it if the - * value of loopCount is > 0. + * Called whenever the activity finishes. Reschedules it if the value of + * loopCount is > 0. */ protected void activityFinished() { setRelativeTargetValueAdjustingForMode(1); @@ -262,6 +291,8 @@ /** * Subclasses should override this method and set the value on their target * (the object that they are modifying) accordingly. + * + * @param zeroToOne relative completion of task. */ public void setRelativeTargetValue(final float zeroToOne) { } @@ -288,25 +319,28 @@ * * @param zeroToOne Percentage of activity completed */ - protected void setRelativeTargetValueAdjustingForMode(float zeroToOne) { + protected void setRelativeTargetValueAdjustingForMode(final float zeroToOne) { + final float adjustedZeroToOne; switch (mode) { - case SOURCE_TO_DESTINATION: - break; - case DESTINATION_TO_SOURCE: - zeroToOne = 1 - zeroToOne; + adjustedZeroToOne = 1 - zeroToOne; break; case SOURCE_TO_DESTINATION_TO_SOURCE: if (zeroToOne <= 0.5) { - zeroToOne *= 2; + adjustedZeroToOne = zeroToOne * 2; } else { - zeroToOne = 1 - (zeroToOne - 0.5f) * 2; + adjustedZeroToOne = 1 - (zeroToOne - 0.5f) * 2; } break; + case SOURCE_TO_DESTINATION: + default: + // Just treat the zeroToOne as how far along the interpolation + // we are. + adjustedZeroToOne = zeroToOne; } - setRelativeTargetValue(zeroToOne); + setRelativeTargetValue(adjustedZeroToOne); } } 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 bc79182..fcdb835 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 @@ -63,7 +63,7 @@ * * @param aTransform the transform to be applied to the target. */ - public void setTransform(AffineTransform aTransform); + void setTransform(AffineTransform aTransform); /** * This method is called right before the transform activity starts. @@ -71,16 +71,42 @@ * * @param aSource array to be populated with the target's gurrent matrix */ - public void getSourceMatrix(double[] aSource); + void getSourceMatrix(double[] aSource); } - public PTransformActivity(final long duration, final long stepRate, final Target aTarget) { - this(duration, stepRate, aTarget, null); + /** + * Constructs a transform activity that will last for the specified + * duration, will update at the given step rate and will be applied to the + * target. + * + * TODO: document what the destination transform is set to when not + * specified. (Looks like the Zero vector, but that can't be right, can it?) + * + * @param duration duration in milliseconds of the entire activity + * @param stepRate interval in milliseconds between successive animation + * steps + * @param target the target of the activity + */ + public PTransformActivity(final long duration, final long stepRate, final Target target) { + this(duration, stepRate, target, null); } - public PTransformActivity(final long duration, final long stepRate, final Target aTarget, - final AffineTransform aDestination) { - this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION, aTarget, aDestination); + /** + * Constructs a activity that will change the target's transform in the + * destination transform. It will last for the specified duration, will + * update at the given step rate. + * + * @param duration duration in milliseconds of the entire activity + * @param stepRate interval in milliseconds between successive animation + * steps + * @param target the target of the activity + * @param destination transform that the target will be after the ativity is + * finished + */ + + public PTransformActivity(final long duration, final long stepRate, final Target target, + final AffineTransform destination) { + this(duration, stepRate, 1, PInterpolatingActivity.SOURCE_TO_DESTINATION, target, destination); } /** @@ -91,21 +117,26 @@ * @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 + * @param target the object that the activity will be applied to and where * the source state will be taken from. - * @param aDestination the destination color state + * @param destination the destination color state */ public PTransformActivity(final long duration, final long stepRate, final int loopCount, final int mode, - final Target aTarget, final AffineTransform aDestination) { + final Target target, final AffineTransform destination) { super(duration, stepRate, loopCount, mode); source = new double[6]; - destination = new double[6]; - target = aTarget; - if (aDestination != null) { - aDestination.getMatrix(destination); + this.destination = new double[6]; + this.target = target; + if (destination != null) { + destination.getMatrix(this.destination); } } + /** + * Whether each step invalidates paint. + * + * @return true since a node transform affects it's node's display + */ protected boolean isAnimation() { return true; } @@ -113,6 +144,8 @@ /** * Return the final transform that will be set on the transform activities * target when the transform activity stops stepping. + * + * @return returns the final transform as an array of doubles */ public double[] getDestinationTransform() { if (destination == null) { @@ -126,6 +159,9 @@ /** * Set the final transform that will be set on the transform activities * target when the transform activity stops stepping. + * + * @param newDestination an array of doubles representing the destination + * transform */ public void setDestinationTransform(final double[] newDestination) { if (newDestination == null) { @@ -136,6 +172,10 @@ } } + /** + * Is invoked when the activity is started. Ensures that setTransform is + * called on the target even before the first step. + */ protected void activityStarted() { if (getFirstLoop()) { target.getSourceMatrix(source); @@ -143,6 +183,17 @@ super.activityStarted(); } + /** + * Set's the target value to be the interpolation between the source and + * destination transforms. + * + * A value of 0 for zeroToOne means that the target should have the source + * transform. A value of 1 for zeroToOne means that the target should have + * the destination transform. + * + * @param zeroToOne how far along the activity has progressed. 0 = not at + * all, 1 = completed + */ public void setRelativeTargetValue(final float zeroToOne) { super.setRelativeTargetValue(zeroToOne); 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 20bd02c..5be3093 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 @@ -48,11 +48,21 @@ private PInputEventFilter eventFilter; + /** + * Constructs a PBasicInputEventHandler with a wide open event filter. + */ public PBasicInputEventHandler() { super(); eventFilter = new PInputEventFilter(); } + /** + * Dispatches a generic event to a more specific method. Sparing subclasses + * from the dispatch logic. + * + * @param event the event to be dispatched + * @param type Swing event type of the underlying Swing event + */ public void processEvent(final PInputEvent event, final int type) { if (!acceptsEvent(event, type)) { return; @@ -127,66 +137,178 @@ // default case) then it accepts all events. // **************************************************************** + /** + * Returns true if the event would be dispatched if passed to processEvent. + * + * @param event event being tested for acceptance + * @param type Swing event type of underlying swing event + * + * @return true if the event would be dispatched + */ public boolean acceptsEvent(final PInputEvent event, final int type) { return eventFilter.acceptsEvent(event, type); } + /** + * Returns the event filter responsible for filtering incoming events. + * + * @return this handler's InputEventFilter + */ public PInputEventFilter getEventFilter() { return eventFilter; } + /** + * Changes this event handler's filter to the one provided. + * + * @param newEventFilter filter to use for this input event handler + */ public void setEventFilter(final PInputEventFilter newEventFilter) { eventFilter = newEventFilter; } - // **************************************************************** - // Events - Methods for handling events sent to the event listener. - // **************************************************************** - + /** + * Will get called whenever a key has been pressed down. Subclasses should + * override this method to implement their own behavior. + * + * @param event the event representing the keystroke + */ public void keyPressed(final PInputEvent event) { } + /** + * Will get called whenever a key has been released. Subclasses should + * override this method to implement their own behavior. + * + * @param event the event representing the keystroke + */ public void keyReleased(final PInputEvent event) { } + /** + * Will be called at the end of a full keystroke (down then up). Subclasses + * should override this method to implement their own behavior. + * + * @param event object which can be queried for the event's details + */ public void keyTyped(final PInputEvent event) { } + /** + * Will be called at the end of a full click (mouse pressed followed by + * mouse released). Subclasses should override this method to implement + * their own behavior. + * + * @param event object which can be queried for the event's details + */ public void mouseClicked(final PInputEvent event) { } + /** + * Will be called when a mouse button is pressed down. Should two buttons be + * pressed simultaneously, it will dispatch two of these in an unspecified + * order. Subclasses should override this method to implement their own + * behavior. + * + * @param event object which can be queried for the event's details + */ public void mousePressed(final PInputEvent event) { } + /** + * Will be called when a drag is occurring. This is system dependent. + * Subclasses should override this method to implement their own behavior. + * + * @param event object which can be queried for the event's details + */ public void mouseDragged(final PInputEvent event) { } + /** + * Will be invoked when the mouse enters a specified region. Subclasses + * should override this method to implement their own behavior. + * + * @param event object which can be queried for the event's details + */ public void mouseEntered(final PInputEvent event) { } + /** + * Will be invoked when the mouse leaves a specified region. Subclasses + * should override this method to implement their own behavior. + * + * @param event object which can be queried for the event's details + */ public void mouseExited(final PInputEvent event) { } + /** + * Will be called when the mouse is moved. Subclasses should override this + * method to implement their own behavior. + * + * @param event object which can be queried for event details + */ public void mouseMoved(final PInputEvent event) { } + /** + * Will be called when any mouse button is released. Should two or more + * buttons be released simultaneously, this method will be called multiple + * times. Subclasses should override this method to implement their own + * behavior. + * + * @param event object which can be queried for event details + */ public void mouseReleased(final PInputEvent event) { } + /** + * This method is invoked when the mouse wheel is rotated. Subclasses should + * override this method to implement their own behavior. + * + * @param event an object that can be queries to discover the event's + * details + */ public void mouseWheelRotated(final PInputEvent event) { } + /** + * This method is invoked when the mouse wheel is rotated by a block. + * Subclasses should override this method to implement their own behavior. + * + * TODO: check that this means 1 tick of the wheel. + * + * @param event an object that can be queries to discover the event's + * details + */ + public void mouseWheelRotatedByBlock(final PInputEvent event) { } + /** + * This method is invoked when a node gains the keyboard focus. Subclasses + * should override this method to implement their own behavior. + * + * @param event an object that can be queries to discover the event's + * details + */ public void keyboardFocusGained(final PInputEvent event) { } + /** + * This method is invoked when a node loses the keyboard focus. Subclasses + * should override this method to implement their own behavior. + * + * @param event an object that can be queries to discover the event's + * details + */ public void keyboardFocusLost(final PInputEvent event) { } /** * @deprecated see http://code.google.com/p/piccolo2d/issues/detail?id=99 + * + * @return empty string since this method is deprecated */ protected String paramString() { return ""; 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 bcbd643..ed8b846 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 @@ -34,9 +34,8 @@ import edu.umd.cs.piccolo.util.PDimension; /** - * PDragEventHandler is a simple event handler for dragging a node on the + * PDragEventHandler is a simple event handler for dragging a node on the * canvas. - *

* * @version 1.0 * @author Jesse Grosjean @@ -44,25 +43,54 @@ public class PDragEventHandler extends PDragSequenceEventHandler { private PNode draggedNode; - private boolean moveToFrontOnPress = false; + private boolean moveToFrontOnPress; + /** + * Constructs a drag event handler which defaults to not moving the node to + * the front on drag. + */ public PDragEventHandler() { - super(); + draggedNode = null; + moveToFrontOnPress = false; + setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK)); } + /** + * Returns the node that is currently being dragged, or null if none. + * + * @return node being dragged or null + */ protected PNode getDraggedNode() { return draggedNode; } + /** + * Set's the node that is currently being dragged. + * + * @param draggedNode node to be flagged as this handler's current drag node + */ protected void setDraggedNode(final PNode draggedNode) { this.draggedNode = draggedNode; } + /** + * Returns whether the given event should be start a drag interaction. + * + * @param event the event being tested + * + * @return true if event is a valid start drag event + */ protected boolean shouldStartDragInteraction(final PInputEvent event) { return super.shouldStartDragInteraction(event) && event.getPickedNode() != event.getTopCamera(); } + /** + * Starts a drag event and moves the dragged node to the front if this + * handler has been directed to do so with a call to setMoveToFrontOnDrag. + * + * @param event The Event responsible for the start of the drag + */ protected void startDrag(final PInputEvent event) { super.startDrag(event); draggedNode = event.getPickedNode(); @@ -71,6 +99,11 @@ } } + /** + * Moves the dragged node in proportion to the drag distance. + * + * @param event event representing the drag + */ protected void drag(final PInputEvent event) { super.drag(event); final PDimension d = event.getDeltaRelativeTo(draggedNode); @@ -78,15 +111,34 @@ draggedNode.offset(d.getWidth(), d.getHeight()); } + /** + * Clears the current drag node. + * + * @param event Event reponsible for the end of the drag. Usually a + * "Mouse Up" event. + */ protected void endDrag(final PInputEvent event) { super.endDrag(event); draggedNode = null; } + /** + * Returns whether this drag event handler has been informed to move nodes + * to the front of all other on drag. + * + * @return true if dragging a node will move it to the front + */ public boolean getMoveToFrontOnPress() { return moveToFrontOnPress; } + /** + * Informs this drag event handler whether it should move nodes to the front + * when they are dragged. Default is false. + * + * @param moveToFrontOnPress true if dragging a node should move it to the + * front + */ public void setMoveToFrontOnPress(final boolean moveToFrontOnPress) { this.moveToFrontOnPress = moveToFrontOnPress; } 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 637f291..8962cc6 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 @@ -56,28 +56,45 @@ private transient PInputEvent dragEvent; private transient int sequenceInitiatedButton = MouseEvent.NOBUTTON; + /** Constructs a drag sequence event handler instance. */ public PDragSequenceEventHandler() { } - // **************************************************************** - // Basics - // **************************************************************** - + /** + * Returns true if this event handler is in the process of handling a drag. + * + * @return true if handling a drag + */ public boolean isDragging() { return isDragging; } + /** + * Used to inform this handler that it is in the process of handling a drag. + * + * @param isDragging true if handler is processing a drag + */ public void setIsDragging(final boolean isDragging) { this.isDragging = isDragging; } + /** + * Returns the minimum distance (in screen coordinates) before a pressed + * mouse movement is registered as a drag event. The smaller this value the + * more clicks will be incorrectly recognized as drag events. + * + * @return minimum distance a pressed mouse must move before it is + * registered as a drag + */ public double getMinDragStartDistance() { return minDragStartDistance; } /** * Set the minimum distance that the mouse should be dragged (in screen - * coords) before a new drag sequence is initiate. + * coordinates) before a new drag sequence is initiate. + * + * @param minDistance in screen coordinates */ public void setMinDragStartDistance(final double minDistance) { minDragStartDistance = minDistance; @@ -85,6 +102,8 @@ /** * Return the point in canvas coordinates where the mouse was last pressed. + * + * @return point in canvas coordinates of last mouse press */ public Point2D getMousePressedCanvasPoint() { if (mousePressedCanvasPoint == null) { @@ -101,37 +120,54 @@ * 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. + * + * @param event event that started the drag sequence */ - protected void startDrag(final PInputEvent e) { - dragEvent = e; - startDragActivity(e); + protected void startDrag(final PInputEvent event) { + dragEvent = event; + startDragActivity(event); setIsDragging(true); - e.getComponent().setInteracting(true); + event.getComponent().setInteracting(true); } /** * 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. + * + * @param event event that caused the drag */ - protected void drag(final PInputEvent e) { - dragEvent = e; + protected void drag(final PInputEvent event) { + dragEvent = event; } /** * 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. + * + * @param event event that ended the drag sequence */ - protected void endDrag(final PInputEvent e) { - stopDragActivity(e); + protected void endDrag(final PInputEvent event) { + stopDragActivity(event); dragEvent = null; - e.getComponent().setInteracting(false); + event.getComponent().setInteracting(false); setIsDragging(false); } - protected boolean shouldStartDragInteraction(final PInputEvent e) { - return getMousePressedCanvasPoint().distance(e.getCanvasPosition()) >= getMinDragStartDistance(); + /** + * Returns true if the provided event represents a valid start for a drag + * sequence. + * + * Subclasses should override this method to add criteria for the start of a + * drag sequence. Subclasses are still responsible for calling + * super.shouldStartDragInteraction() + * + * @param event event being tested + * @return true if provided event is a good start to a drag sequence + */ + protected boolean shouldStartDragInteraction(final PInputEvent event) { + return getMousePressedCanvasPoint().distance(event.getCanvasPosition()) >= getMinDragStartDistance(); } // **************************************************************** @@ -140,11 +176,27 @@ // using this. // **************************************************************** + /** + * Returns the scheduled activity that's updating the scene as a result to + * the current drag activity (if any). + * + * @return scheduled activity that's updating the scene as a result to the + * drag activity + */ protected PActivity getDragActivity() { return dragActivity; } - protected void startDragActivity(final PInputEvent aEvent) { + /** + * Schedules the "infinite" drag activity so that auto-panning and zooming + * will continue to update the scene even if there are no further drag + * events fired. For example, if the mouse is dragged to the right while + * pressing the right mouse button and then paused for a while, the scene + * should continue to zoom in. + * + * @param event the event that's responsible for the start of the activity + */ + protected void startDragActivity(final PInputEvent event) { dragActivity = new PActivity(-1, PUtil.DEFAULT_ACTIVITY_STEP_RATE); dragActivity.setDelegate(new PActivity.PActivityDelegate() { public void activityStarted(final PActivity activity) { @@ -160,19 +212,27 @@ } }); - aEvent.getCamera().getRoot().addActivity(dragActivity); + event.getCamera().getRoot().addActivity(dragActivity); } - protected void stopDragActivity(final PInputEvent aEvent) { + /** + * Stops the activity responsible for updating the scene. + * + * @param event The event responsible for stopping the drag activity + */ + protected void stopDragActivity(final PInputEvent event) { dragActivity.terminate(); dragActivity = null; } /** - * Override this method to get notified when the drag activity starts - * stepping. + * Subclasses override this method to get notified when the drag activity + * starts stepping. + * + * @param event the event responsible for the first step in the drag + * activity */ - protected void dragActivityFirstStep(final PInputEvent aEvent) { + protected void dragActivityFirstStep(final PInputEvent event) { } /** @@ -181,57 +241,72 @@ * additional behavior that is not driven directly by mouse events. For * example PZoomEventHandler uses it for zooming and PPanEventHandler uses * it for auto panning. + * + * @param event the event encapsulating the callback context for the + * activity step */ - protected void dragActivityStep(final PInputEvent aEvent) { + protected void dragActivityStep(final PInputEvent event) { } /** - * Override this method to get notified when the drag activity stops - * stepping. + * Subclasses should override this method to get notified when the drag + * activity stops stepping. + * + * @param aEvent the event responsible for ending the activity */ protected void dragActivityFinalStep(final PInputEvent aEvent) { } - // **************************************************************** - // Events - subclasses should not override these methods, instead - // override the appropriate drag method. - // **************************************************************** - - public void mousePressed(final PInputEvent e) { - super.mousePressed(e); + /** + * Subclasses should not override this method, instead they should + * override the appropriate drag callbacks. + * + * @param event The event to be queried about the details of the mouse press + */ + public void mousePressed(final PInputEvent event) { + super.mousePressed(event); if (sequenceInitiatedButton == MouseEvent.NOBUTTON) { - sequenceInitiatedButton = e.getButton(); - } - else { - return; - } - - getMousePressedCanvasPoint().setLocation(e.getCanvasPosition()); - if (!isDragging() && shouldStartDragInteraction(e)) { - startDrag(e); + sequenceInitiatedButton = event.getButton(); + + getMousePressedCanvasPoint().setLocation(event.getCanvasPosition()); + if (!isDragging() && shouldStartDragInteraction(event)) { + startDrag(event); + } } } - public void mouseDragged(final PInputEvent e) { - super.mouseDragged(e); + /** + * Subclasses should not override this method, instead they should + * override the appropriate drag method. + * + * @param event The event to be queried about the details of the mouse press + */ + public void mouseDragged(final PInputEvent event) { + super.mouseDragged(event); if (sequenceInitiatedButton != MouseEvent.NOBUTTON) { if (!isDragging()) { - if (shouldStartDragInteraction(e)) { - startDrag(e); + if (shouldStartDragInteraction(event)) { + startDrag(event); } return; } - drag(e); + drag(event); } } - public void mouseReleased(final PInputEvent e) { - super.mouseReleased(e); - if (sequenceInitiatedButton == e.getButton()) { + /** + * Subclasses should not override this method, instead they should + * override the appropriate drag method. + * + * @param event The event to be queried about the details of the mouse release + */ + public void mouseReleased(final PInputEvent event) { + super.mouseReleased(event); + if (sequenceInitiatedButton == event.getButton()) { if (isDragging()) { - endDrag(e); + endDrag(event); } sequenceInitiatedButton = MouseEvent.NOBUTTON; } 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 388f547..08cf879 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 @@ -79,8 +79,7 @@ * @param inputManager source of PInputEvent * @param event underlying swing event */ - public PInputEvent(final PInputManager inputManager, final InputEvent event) { - super(); + public PInputEvent(final PInputManager inputManager, final InputEvent event) { inputEvent = event; this.inputManager = inputManager; } @@ -184,6 +183,9 @@ * @return the currently picked node of this mouse event */ public PNode getPickedNode() { + if (pickPath == null) { + return null; + } return pickPath.getPickedNode(); } @@ -499,7 +501,6 @@ * * @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()) { @@ -544,6 +545,9 @@ * @return mouse position relative to the provided node on pick path */ public Point2D getPositionRelativeTo(final PNode nodeOnPath) { + if (pickPath == null) { + throw new RuntimeException("Attempting to use pickPath for a non-mouse event."); + } final Point2D r = getCanvasPosition(); return pickPath.canvasToLocal(r, nodeOnPath); } @@ -557,6 +561,9 @@ * path */ public PDimension getDeltaRelativeTo(final PNode nodeOnPath) { + if (pickPath == null) { + throw new RuntimeException("Attempting to use pickPath for a non-mouse event."); + } final PDimension r = getCanvasDelta(); return (PDimension) pickPath.canvasToLocal(r, nodeOnPath); } @@ -568,6 +575,9 @@ * @return mouse position as measured by the bottom camera */ public Point2D getPosition() { + if (pickPath == null) { + throw new RuntimeException("Attempting to use pickPath for a non-mouse event."); + } final Point2D r = getCanvasPosition(); pickPath.canvasToLocal(r, getCamera()); return getCamera().localToView(r); @@ -581,6 +591,9 @@ * bottom camera */ public PDimension getDelta() { + if (pickPath == null) { + throw new RuntimeException("Attempting to use pickPath for a non-mouse event."); + } final PDimension r = getCanvasDelta(); pickPath.canvasToLocal(r, getCamera()); return (PDimension) getCamera().localToView(r); 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 65a82b5..0e0caf1 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 @@ -48,27 +48,43 @@ */ public class PPanEventHandler extends PDragSequenceEventHandler { - private boolean autopan; - private double minAutopanSpeed = 250; - private double maxAutopanSpeed = 750; + private static final int DEFAULT_MAX_AUTOPAN_SPEED = 750; + private static final int DEFAULT_MIN_AUTOPAN_SPEED = 250; + private boolean autopan; + private double minAutopanSpeed = DEFAULT_MIN_AUTOPAN_SPEED; + private double maxAutopanSpeed = DEFAULT_MAX_AUTOPAN_SPEED; + + /** + * Constructs a Pan Event Handler that will by default perform auto-panning. + */ public PPanEventHandler() { super(); setEventFilter(new PInputEventFilter(InputEvent.BUTTON1_MASK)); setAutopan(true); } - protected void drag(final PInputEvent e) { - super.drag(e); - pan(e); + /** + * Updates the view in response to a user initiated drag event. + * + * @param event event responsible for the drag + */ + protected void drag(final PInputEvent event) { + super.drag(event); + pan(event); } - protected void pan(final PInputEvent e) { - final PCamera c = e.getCamera(); - final Point2D l = e.getPosition(); + /** + * Pans the camera in response to the pan event provided. + * + * @param event contains details about the drag used to translate the view + */ + protected void pan(final PInputEvent event) { + final PCamera c = event.getCamera(); + final Point2D l = event.getPosition(); if (c.getViewBounds().contains(l)) { - final PDimension d = e.getDelta(); + final PDimension d = event.getDelta(); c.translateView(d.getWidth(), d.getHeight()); } } @@ -77,10 +93,20 @@ // Auto Pan // **************************************************************** + /** + * Determines if auto-panning will occur or not. + * + * @param autopan true if auto-panning functionality will be active + */ public void setAutopan(final boolean autopan) { this.autopan = autopan; } + /** + * Returns whether the auto-panning functoinality is enabled. + * + * @return true if auto-panning is enabled + */ public boolean getAutopan() { return autopan; } @@ -88,16 +114,18 @@ /** * Set the minAutoPan speed in pixels per second. * - * @param minAutopanSpeed + * @param minAutopanSpeed number of pixels to assign as the minimum the + * autopan feature can pan the view */ public void setMinAutopanSpeed(final double minAutopanSpeed) { this.minAutopanSpeed = minAutopanSpeed; } /** - * Set the maxAutoPan speed in pixes per second. + * Set the maxAutoPan speed in pixels per second. * - * @param maxAutopanSpeed + * @param maxAutopanSpeed number of pixels to assign as the maximum the + * autopan feature can pan the view */ public void setMaxAutopanSpeed(final double maxAutopanSpeed) { this.maxAutopanSpeed = maxAutopanSpeed; @@ -106,7 +134,7 @@ /** * Returns the minAutoPan speed in pixels per second. * - * @return minAutopanSpeed in pixels + * @return minimum distance the autopan feature can pan the view */ public double getMinAutoPanSpeed() { return minAutopanSpeed; @@ -115,23 +143,25 @@ /** * Returns the maxAutoPan speed in pixels per second. * - * @return maxAutopanSpeed in pixels + * @return max distance the autopan feature can pan the view by */ public double getMaxAutoPanSpeed() { return maxAutopanSpeed; } /** - * Do auto panning even when the mouse is not moving. + * Performs auto-panning if enabled, even when the mouse is not moving. + * + * @param event current drag relevant details about the drag activity */ - protected void dragActivityStep(final PInputEvent aEvent) { + protected void dragActivityStep(final PInputEvent event) { if (!autopan) { return; } - final PCamera c = aEvent.getCamera(); + final PCamera c = event.getCamera(); final PBounds b = c.getBoundsReference(); - final Point2D l = aEvent.getPositionRelativeTo(c); + final Point2D l = event.getPositionRelativeTo(c); final int outcode = b.outcode(l); final PDimension delta = new PDimension(); @@ -156,21 +186,37 @@ } } - protected double validatePanningSpeed(double delta) { - final double minDelta = minAutopanSpeed / (1000d / getDragActivity().getStepRate()); - final double maxDelta = maxAutopanSpeed / (1000d / getDragActivity().getStepRate()); + /** + * Clips the panning speed to the minimum and maximum auto-pan speeds + * assigned. If delta is below the threshold, it will be increased. If + * above, it will be decreased. + * + * @param delta auto-pan delta to be clipped + * @return clipped delta value. + */ + protected double validatePanningSpeed(final double delta) { + final double stepsPerSecond = 1000d / getDragActivity().getStepRate(); + final double minDelta = minAutopanSpeed / stepsPerSecond; + final double maxDelta = maxAutopanSpeed / stepsPerSecond; - final boolean deltaNegative = delta < 0; - delta = Math.abs(delta); - if (delta < minDelta) { - delta = minDelta; + final double absDelta = Math.abs(delta); + + final double clippedDelta; + if (absDelta < minDelta) { + clippedDelta = minDelta; } - if (delta > maxDelta) { - delta = maxDelta; + else if (absDelta > maxDelta) { + clippedDelta = maxDelta; } - if (deltaNegative) { - delta = -delta; + else { + clippedDelta = delta; } - return delta; + + if (delta < 0) { + return -clippedDelta; + } + else { + return clippedDelta; + } } } 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 640b3de..11251c2 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 @@ -59,6 +59,11 @@ */ public class PZoomEventHandler extends PDragSequenceEventHandler { + /** + * A constant used to adjust how sensitive the zooming will be to mouse + * movement. The larger the number, the more each delta pixel will affect zooming. + */ + private static final double ZOOM_SENSITIVITY = 0.001; private double minScale = 0; private double maxScale = Double.MAX_VALUE; private Point2D viewZoomPoint; @@ -118,15 +123,27 @@ this.maxScale = maxScale; } - protected void dragActivityFirstStep(final PInputEvent aEvent) { - viewZoomPoint = aEvent.getPosition(); - super.dragActivityFirstStep(aEvent); + /** + * Records the start point of the zoom. Used when calculating the delta for + * zoom speed. + * + * @param event event responsible for starting the zoom interaction + */ + protected void dragActivityFirstStep(final PInputEvent event) { + viewZoomPoint = event.getPosition(); + super.dragActivityFirstStep(event); } - protected void dragActivityStep(final PInputEvent aEvent) { - final PCamera camera = aEvent.getCamera(); - final double dx = aEvent.getCanvasPosition().getX() - getMousePressedCanvasPoint().getX(); - double scaleDelta = 1.0 + 0.001 * dx; + /** + * Updates the current zoom periodically, regardless of whether the mouse + * has moved recently. + * + * @param event contains information about the current state of the mouse + */ + protected void dragActivityStep(final PInputEvent event) { + final PCamera camera = event.getCamera(); + final double dx = event.getCanvasPosition().getX() - getMousePressedCanvasPoint().getX(); + double scaleDelta = 1.0 + ZOOM_SENSITIVITY * dx; final double currentScale = camera.getViewScale(); final double newScale = currentScale * scaleDelta; diff --git a/core/src/main/java/edu/umd/cs/piccolo/nodes/PHtmlView.java b/core/src/main/java/edu/umd/cs/piccolo/nodes/PHtmlView.java index 215355e..51a0476 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/nodes/PHtmlView.java +++ b/core/src/main/java/edu/umd/cs/piccolo/nodes/PHtmlView.java @@ -230,15 +230,13 @@ * This text color is used to render the HTML text if not otherwise * specified via CSS. * - *

* This is a bound property. - *

* * @param textColor text color for this HTML text node */ - public void setTextColor(final Color color) { + public void setTextColor(final Color textColor) { final Color oldColor = label.getForeground(); - label.setForeground(color); + label.setForeground(textColor); repaint(); firePropertyChange(PROPERTY_CODE_TEXT_COLOR, PROPERTY_TEXT_COLOR, oldColor, label.getForeground()); } 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 7426f54..bc6c625 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 @@ -69,47 +69,57 @@ * to Image objects in any property change event. */ public static final String PROPERTY_IMAGE = "image"; + /** + * The property code 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 int PROPERTY_CODE_IMAGE = 1 << 15; private transient Image image; + /** Constructs a PImage without a java.awt.Image attached. */ public PImage() { - super(); - } - - /** - * Construct a new PImage wrapping the given java.awt.Image. - */ - public PImage(final Image newImage) { - this(); - setImage(newImage); } /** * Construct a new PImage by loading the given fileName and wrapping the * resulting java.awt.Image. + * + * @param fileName of the image to wrap */ public PImage(final String fileName) { this(Toolkit.getDefaultToolkit().getImage(fileName)); } /** + * Construct a new PImage wrapping the given java.awt.Image. + * + * @param image image that this PImage will wrap + */ + public PImage(final Image image) { + setImage(image); + } + + /** * 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. + * + * @param url URL of image resource to load */ public PImage(final java.net.URL url) { - this(); if (url != null) { setImage(Toolkit.getDefaultToolkit().getImage(url)); } } /** - * Returns the image that is shown by this node. + * Returns the image that is shown by this node, or null if none. * - * @return the image that is shown by this node + * @return java.awt.Image being wrapped by this node */ public Image getImage() { return image; @@ -118,6 +128,8 @@ /** * Set the image that is wrapped by this PImage node. This method will also * load the image using a MediaTracker before returning. + * + * @param fileName file to be wrapped by this PImage */ public void setImage(final String fileName) { setImage(Toolkit.getDefaultToolkit().getImage(fileName)); @@ -126,6 +138,8 @@ /** * Set the image that is wrapped by this PImage node. This method will also * load the image using a MediaTracker before returning. + * + * @param newImage image to be displayed by this PImage */ public void setImage(final Image newImage) { final Image oldImage = image; @@ -146,7 +160,7 @@ } /** - * Ensures the image is loaded enough (loading is fine) + * Ensures the image is loaded enough (loading is fine). * * @param newImage to check * @return image or null if not loaded enough. @@ -155,7 +169,6 @@ final ImageIcon imageLoader = new ImageIcon(newImage); switch (imageLoader.getImageLoadStatus()) { case MediaTracker.LOADING: - return imageLoader.getImage(); case MediaTracker.COMPLETE: return imageLoader.getImage(); default: @@ -163,6 +176,12 @@ } } + /** + * Renders the wrapped Image, stretching it appropriately if the bounds of + * this PImage doesn't match the bounds of the image. + * + * @param paintContext context into which the rendering will occur + */ protected void paint(final PPaintContext paintContext) { if (getImage() == null) { return; @@ -187,13 +206,12 @@ } - // **************************************************************** - // Serialization - // **************************************************************** - /** - * The java.awt.Image wrapped by this PImage is converted into a - * BufferedImage when serialized. + * Serializes this PImage to the stream provided. The java.awt.Image wrapped + * by this PImage is converted into a BufferedImage when serialized. + * + * @param out stream into which serialized object will be serialized + * @throws IOException if error occurs while writing to the output stream */ private void writeObject(final ObjectOutputStream out) throws IOException { out.defaultWriteObject(); @@ -203,19 +221,28 @@ } } + /** + * Deserializes a PImage from the input stream provided. + * + * @param in stream from which the PImage should be read + * @throws IOException if problem occurs while reading from input stream + * @throws ClassNotFoundException occurs is no mapping from the bytes in the + * stream can be found to classes available + */ private void readObject(final 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. + * Converts the provided image into a BufferedImage. 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. + * + * @param image the image to be converted + * @param alwaysCreateCopy if true, will create a copy even if image is + * already a BufferedImage + * @return a BufferedImage equivalent to the Image provided */ public static BufferedImage toBufferedImage(final Image image, final boolean alwaysCreateCopy) { if (image == null) { 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 abf7c10..c62dee4 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 @@ -244,10 +244,10 @@ } /** - * Creates an PPath in the given shape with the default paint and stroke. - * - * @param aShape the desired shape - */ + * Creates an PPath in the given shape with the default paint and stroke. + * + * @param aShape the desired shape + */ public PPath(final Shape aShape) { this(aShape, DEFAULT_STROKE); } @@ -272,10 +272,6 @@ } } - // **************************************************************** - // Stroke - // **************************************************************** - /** * Returns the stroke paint of the PPath. * @@ -285,17 +281,32 @@ return strokePaint; } - public void setStrokePaint(final Paint aPaint) { - final Paint old = strokePaint; - strokePaint = aPaint; + /** + * Sets the stroke paint of the path. + * + * @param newStrokePaint the paint to use as this path's stroke paint + */ + public void setStrokePaint(final Paint newStrokePaint) { + final Paint oldStrokePaint = strokePaint; + strokePaint = newStrokePaint; invalidatePaint(); - firePropertyChange(PROPERTY_CODE_STROKE_PAINT, PROPERTY_STROKE_PAINT, old, strokePaint); + firePropertyChange(PROPERTY_CODE_STROKE_PAINT, PROPERTY_STROKE_PAINT, oldStrokePaint, strokePaint); } + /** + * Returns the stroke to use when drawing the path. + * + * @return current stroke of path + */ public Stroke getStroke() { return stroke; } + /** + * Sets the stroke to use when drawing the path. + * + * @param aStroke stroke to use when drawing the path + */ public void setStroke(final Stroke aStroke) { final Stroke old = stroke; stroke = aStroke; @@ -304,14 +315,12 @@ firePropertyChange(PROPERTY_CODE_STROKE, PROPERTY_STROKE, old, stroke); } - // **************************************************************** - // Bounds - // **************************************************************** - + /** Stores the original size of the path before resizing started. */ public void startResizeBounds() { resizePath = new GeneralPath(path); } + /** Clears the size of the path before resizing. */ public void endResizeBounds() { resizePath = null; } @@ -322,8 +331,13 @@ * 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. + * + * @param x new left position of bounds + * @param y new top position of bounds + * @param width the new width of the bounds + * @param height the new height of the bounds */ - protected void internalUpdateBounds(double x, double y, double width, double height) { + protected void internalUpdateBounds(final double x, final double y, final double width, final double height) { if (updatingBoundsFromPath || path == null) { return; } @@ -339,22 +353,42 @@ .getHeight() - pathBounds.getHeight()); - x += strokeOutset / 2; - y += strokeOutset / 2; - width -= strokeOutset; - height -= strokeOutset; + double adjustedX = x + strokeOutset / 2; + double adjustedY = y + strokeOutset / 2; + double adjustedWidth = width - strokeOutset; + double adjustedHeight = height - strokeOutset; - final double scaleX = width == 0 || pathBounds.getWidth() == 0 ? 1 : width / pathBounds.getWidth(); - final double scaleY = height == 0 || pathBounds.getHeight() == 0 ? 1 : height / pathBounds.getHeight(); + final double scaleX; + if (adjustedWidth == 0 || pathBounds.getWidth() == 0) { + scaleX = 1; + } + else { + scaleX = adjustedWidth / pathBounds.getWidth(); + } + + final double scaleY; + if (adjustedHeight == 0 || pathBounds.getHeight() == 0) { + scaleY = 1; + } + else { + scaleY = adjustedHeight / pathBounds.getHeight(); + } TEMP_TRANSFORM.setToIdentity(); - TEMP_TRANSFORM.translate(x, y); + TEMP_TRANSFORM.translate(adjustedX, adjustedY); TEMP_TRANSFORM.scale(scaleX, scaleY); TEMP_TRANSFORM.translate(-pathBounds.getX(), -pathBounds.getY()); path.transform(TEMP_TRANSFORM); } + /** + * Returns true if path crosses the provided bounds. Takes visibility of + * path into account. + * + * @param aBounds bounds being tested for intersection + * @return true if path visibly crosses bounds + */ public boolean intersects(final Rectangle2D aBounds) { if (super.intersects(aBounds)) { if (getPaint() != null && path.intersects(aBounds)) { @@ -367,6 +401,11 @@ return false; } + /** + * Calculates the path's bounds taking stroke into account. + * + * @return bounds of the path taking stroke width into account + */ public Rectangle2D getPathBoundsWithStroke() { if (stroke != null) { return stroke.createStrokedShape(path).getBounds2D(); @@ -376,6 +415,9 @@ } } + /** + * Recomputes the bounds taking stroke into account. + */ public void updateBoundsFromPath() { updatingBoundsFromPath = true; if (path == null) { @@ -388,10 +430,15 @@ updatingBoundsFromPath = false; } - // **************************************************************** - // Painting - // **************************************************************** - + /** + * Paints the path in the provided paintContext. Can perform very + * differently depending on whether the path is being drawn using its stroke + * or its paint. + * + * It both are provided to the path, fun ensues. + * + * @param paintContext context in which painting is occurring + */ protected void paint(final PPaintContext paintContext) { final Paint p = getPaint(); final Graphics2D g2 = paintContext.getGraphics(); @@ -408,15 +455,21 @@ } } - // **************************************************************** - // Path Support set java.awt.GeneralPath documentation for more - // information on using these methods. - // **************************************************************** - + /** + * Provides direct access to the underlying GeneralPath object. + * + * @return underlying GeneralPath + */ public GeneralPath getPathReference() { return path; } + /** + * Appends a "move" operation to the end of the path. + * + * @param x the x component of the point to move to + * @param y the y component of the point to move to + */ public void moveTo(final float x, final float y) { path.moveTo(x, y); firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path); @@ -424,6 +477,12 @@ invalidatePaint(); } + /** + * Draws a line from the last point in the path to point provided. + * + * @param x the x component of the point + * @param y the y component of the point + */ public void lineTo(final float x, final float y) { path.lineTo(x, y); firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path); @@ -431,6 +490,14 @@ invalidatePaint(); } + /** + * Appends a quad line to the end of the path. + * + * @param x1 + * @param y1 + * @param x2 + * @param y2 + */ public void quadTo(final float x1, final float y1, final float x2, final float y2) { path.quadTo(x1, y1, x2, y2); firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path); @@ -438,6 +505,16 @@ invalidatePaint(); } + /** + * Appends a curve to the end of the path. + * + * @param x1 + * @param y1 + * @param x2 + * @param y2 + * @param x3 + * @param y3 + */ public void curveTo(final float x1, final float y1, final float x2, final float y2, final float x3, final float y3) { path.curveTo(x1, y1, x2, y2, x3, y3); firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path); @@ -445,6 +522,14 @@ invalidatePaint(); } + /** + * Appends the provided shape to the end of this path, it may conditionally + * connect them together if they are disjoint. + * + * @param aShape shape to append + * @param connect whether to perform a lineTo operation to the beginning of + * the shape before appending + */ public void append(final Shape aShape, final boolean connect) { path.append(aShape, connect); firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path); @@ -452,21 +537,48 @@ invalidatePaint(); } + /** + * Replaces this PPath's path with the one provided. + * + * @param aShape shape to replace the current one with + */ public void setPathTo(final Shape aShape) { path.reset(); append(aShape, false); } + /** + * Resets the path to a rectangle with the dimensions and position provided. + * + * @param x left of the rectangle + * @param y top of te rectangle + * @param width width of the rectangle + * @param height height of the rectangle + */ public void setPathToRectangle(final float x, final float y, final float width, final float height) { TEMP_RECTANGLE.setFrame(x, y, width, height); setPathTo(TEMP_RECTANGLE); } + /** + * Resets the path to an ellipse positioned at the coordinate provided with + * the dimensions provided. + * + * @param x left of the ellipse + * @param y top of the ellipse + * @param width width of the ellipse + * @param height height of the ellipse + */ public void setPathToEllipse(final float x, final float y, final float width, final float height) { TEMP_ELLIPSE.setFrame(x, y, width, height); setPathTo(TEMP_ELLIPSE); } + /** + * Sets the path to a sequence of segments described by the points. + * + * @param points points to that lie along the generated path + */ public void setPathToPolyline(final Point2D[] points) { path.reset(); path.moveTo((float) points[0].getX(), (float) points[0].getY()); @@ -478,6 +590,13 @@ invalidatePaint(); } + /** + * Sets the path to a sequence of segments described by the point components + * provided. + * + * @param xp the x components of the points along the path + * @param yp the y components of the points along the path + */ public void setPathToPolyline(final float[] xp, final float[] yp) { path.reset(); path.moveTo(xp[0], yp[0]); @@ -489,6 +608,9 @@ invalidatePaint(); } + /** + * Marks the path as closed. Making changes to it impossible. + */ public void closePath() { path.closePath(); firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path); @@ -496,6 +618,9 @@ invalidatePaint(); } + /** + * Empties the path. + */ public void reset() { path.reset(); firePropertyChange(PROPERTY_CODE_PATH, PROPERTY_PATH, null, path); @@ -503,16 +628,28 @@ invalidatePaint(); } - // **************************************************************** - // Serialization - // **************************************************************** - + /** + * Writes this PPath object to the output stream provided. Necessary since + * stroke and path are not serializable by default. + * + * @param out output stream into which objects are to be serialized + * @throws IOException if serialiazing to output stream fails + */ private void writeObject(final ObjectOutputStream out) throws IOException { out.defaultWriteObject(); PUtil.writeStroke(stroke, out); PUtil.writePath(path, out); } + /** + * Deserializes a PPath object from the provided input stream. This method + * is required since Strokes and GeneralPaths are not serializable by + * default. + * + * @param in stream from which to read this PPath's state + * @throws IOException when exception occurs reading from input stream + * @throws ClassNotFoundException + */ private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); stroke = PUtil.readStroke(in); 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 4c95b76..b7b4276 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 @@ -115,7 +115,7 @@ /** * Returns a clone of this node. */ - public Object clone() { + public Object clone() { return new PBounds(this); } 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 8137d6a..bacf518 100644 --- a/core/src/test/java/edu/umd/cs/piccolo/PNodeTest.java +++ b/core/src/test/java/edu/umd/cs/piccolo/PNodeTest.java @@ -194,18 +194,55 @@ assertEquals(node, parent.getChild(2)); } - public void testCopy() { + public void testCloneCopiesAllProperties() { + node.setBounds(1, 2, 3, 4); + node.setChildPaintInvalid(true); + node.setChildrenPickable(false); node.setPaint(Color.yellow); + node.setPaintInvalid(true); + node.setPickable(false); + node.setPropertyChangeParentMask(PNode.PROPERTY_CODE_PAINT); + node.setVisible(false); + + final PNode clonedNode = (PNode) node.clone(); + assertEquals(1, clonedNode.getX(), Double.MIN_VALUE); + assertEquals(2, clonedNode.getY(), Double.MIN_VALUE); + assertEquals(3, clonedNode.getWidth(), Double.MIN_VALUE); + assertEquals(4, clonedNode.getHeight(), Double.MIN_VALUE); + assertTrue(clonedNode.getChildPaintInvalid()); + assertFalse(clonedNode.getChildrenPickable()); + assertEquals(Color.YELLOW, clonedNode.getPaint()); + + assertFalse(clonedNode.getPickable()); + assertEquals(PNode.PROPERTY_CODE_PAINT, node.getPropertyChangeParentMask()); + assertFalse(clonedNode.getVisible()); + } + + public void testCloneCopiesTransforms() { + node.setScale(0.5); + node.setRotation(Math.PI/8d); + node.setOffset(5,6); + + final PNode clonedNode = (PNode) node.clone(); + + assertEquals(0.5, clonedNode.getScale(), 0.00001); + assertEquals(Math.PI/8d, clonedNode.getRotation(), 0.00001); + assertEquals(5, clonedNode.getXOffset(), Double.MIN_VALUE); + assertEquals(6, clonedNode.getYOffset(), Double.MIN_VALUE); + } + + public void testCloneClonesChildrenAswell() { final PNode child = new PNode(); node.addChild(child); final PNode clonedNode = (PNode) node.clone(); - - assertEquals(clonedNode.getPaint(), Color.yellow); + assertEquals(clonedNode.getChildrenCount(), 1); + assertNotSame(child, clonedNode.getChild(0)); } + public void testLocalToGlobal() { final PNode aParent = new PNode(); final PNode aChild = new PNode(); @@ -1043,10 +1080,10 @@ } - public void testToImageScalesAccordingToAspectCoverStrategy() throws IOException { + public void testToImageScalesAccordingToAspectCoverStrategy() throws IOException { node.setBounds(0, 0, 10, 10); node.setPaint(Color.RED); - + PNode blueSquare = new PNode(); blueSquare.setPaint(Color.BLUE); blueSquare.setBounds(0, 0, 5, 5); @@ -1057,7 +1094,6 @@ greenSquare.setBounds(5, 5, 5, 5); node.addChild(greenSquare); - final BufferedImage img = (BufferedImage) node.toImage(new BufferedImage(20, 40, BufferedImage.TYPE_INT_RGB), Color.BLUE, PNode.FILL_STRATEGY_EXACT_FIT); @@ -1065,18 +1101,18 @@ assertEquals(Color.RED.getRGB(), img.getRGB(9, 20)); assertEquals(Color.RED.getRGB(), img.getRGB(0, 20)); assertEquals(Color.RED.getRGB(), img.getRGB(9, 39)); - + assertEquals(Color.BLUE.getRGB(), img.getRGB(9, 19)); assertEquals(Color.BLUE.getRGB(), img.getRGB(0, 0)); assertEquals(Color.BLUE.getRGB(), img.getRGB(0, 19)); assertEquals(Color.BLUE.getRGB(), img.getRGB(9, 0)); - - assertEquals(Color.GREEN.getRGB(), img.getRGB(10, 20)); + + assertEquals(Color.GREEN.getRGB(), img.getRGB(10, 20)); assertEquals(Color.GREEN.getRGB(), img.getRGB(19, 20)); assertEquals(Color.GREEN.getRGB(), img.getRGB(10, 39)); assertEquals(Color.GREEN.getRGB(), img.getRGB(19, 39)); } - + public void testGetPickableShouldDefaultToTrue() { assertTrue(node.getPickable()); } @@ -1381,8 +1417,8 @@ final PPickPath path = canvas.getCamera().pick(5, 5, 5); assertSame(node1, path.getPickedNode()); } - + public void testToImageDoesNotClip() { - + } }