Hi,
The exception below occurs when I'm trying to unmarshal an xml file.
It's trying to handle a class Paint which is not even annotated as
JAXB object (using XmlAccessType.NONE for all my classes). Here is my
node class. It seems JAXB is trying to bind classes although they are
not annotated, neither property nor method.
@XmlType(name = "Node")
@javax.xml.bind.annotation.XmlAccessorType(
javax.xml.bind.annotation.XmlAccessType.NONE
)
public class Node extends PNode implements IModel {
/**
*GeneratedserialversionUID.
*/
private static final long serialVersionUID = -2536995926503181689L;
/**
*DefaultconstructoratleastrequiredforJAXB.
*/
public Node() {
// empty
}
@XmlID
@XmlAttribute(name = "id", namespace = "
http://www.omg.org/XMI")
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@XmlIDREF
@XmlAttribute(name = "element")
private PumpingStation element;
public PumpingStation getElement() {
return element;
}
public void setElement(PumpingStation element) {
this.element = element;
}
@XmlElement(name = "layout-constraint")
private LayoutConstraint layoutConstraint;
public LayoutConstraint getLayoutConstraint() {
return layoutConstraint;
}
public void setLayoutConstraint(LayoutConstraint layoutConstraint) {
this.layoutConstraint = layoutConstraint;
}
// some other methods and properties like protected void
paint(PaintContext context)... that are not annotated either.
}
Here is the package-info.java:
@javax.xml.bind.annotation.XmlSchema(
xmlns = {
@XmlNs(prefix = "notation", namespaceURI = "
http://www.eclipse.org/gmf/runtime/1.0.1/notation
"),
@XmlNs(prefix = "Designer", namespaceURI = "
http://www.inf.uni-konstanz.de/hci/designer
"),
@XmlNs(prefix = "xsi", namespaceURI = "
http://www.w3.org/2001/XMLSchema-instance
"),
@XmlNs(prefix = "xmi", namespaceURI = "
http://www.omg.org/
XMI")
},
namespace = "
http://www.inf.uni-konstanz.de/hci/designer"
)
@javax.xml.bind.annotation.XmlAccessorType(
javax.xml.bind.annotation.XmlAccessType.NONE
) package de.ukn.hci.interaction.design.parser.diagram.node;
import javax.xml.bind.annotation.XmlNs;
PNode source code is attached below.
com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2 counts of
IllegalAnnotationExceptions
java.awt.Paint is an interface, and JAXB can't handle interfaces.
this problem is related to the following location:
at java.awt.Paint
at public java.awt.Paint edu.umd.cs.piccolo.PNode.getPaint()
at edu.umd.cs.piccolo.PNode
at my.project.interaction.design.parser.diagram.node.Node
at private java.util.Collection
my.project.interaction.design.parser.diagram.Diagram.children
at my.project.interaction.design.parser.diagram.Diagram
at private my.project.interaction.design.parser.diagram.Diagram
my.project.interaction.design.parser.xmi.XMI.diagram
at my.project.interaction.design.parser.xmi.XMI
java.awt.Paint does not have a no-arg default constructor.
this problem is related to the following location:
at java.awt.Paint
at public java.awt.Paint edu.umd.cs.piccolo.PNode.getPaint()
at edu.umd.cs.piccolo.PNode
at my.project.interaction.design.parser.diagram.node.Node
at private java.util.Collection
my.project.interaction.design.parser.diagram.Diagram.children
at my.project.interaction.design.parser.diagram.Diagram
at private my.project.interaction.design.parser.diagram.Diagram
my.project.interaction.design.parser.xmi.XMI.diagram
at my.project.interaction.design.parser.xmi.XMI
at com.sun.xml.bind.v2.runtime.IllegalAnnotationsException
$Builder.check(IllegalAnnotationsException.java:66)
at
com
.sun
.xml
.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:
422)
at
com
.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:
270)
at
com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:
103)
at
com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:81)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
sun
.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:
39)
at
sun
.reflect
.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:
25)
at java.lang.reflect.Method.invoke(Method.java:585)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:210)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:366)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:522)
at
my
.project
.interaction
.design.parser.DiagramModelHandler.getContext(DiagramModelHandler.java:
74)
at
my
.project
.interaction
.design.parser.DiagramModelHandler.load(DiagramModelHandler.java:88)
at
my
.project
.interaction
.design.parser.DiagramModelHandler.main(DiagramModelHandler.java:125)
/*
* Copyright (c) 2002-_at_year@, University of Maryland
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
modification, are permitted provided
* that the following conditions are met:
*
* Redistributions of source code must retain the above copyright
notice, this list of conditions
* and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions
* and the following disclaimer in the documentation and/or other
materials provided with the
* distribution.
*
* Neither the name of the University of Maryland nor the names of
its contributors may be used to
* endorse or promote products derived from this software without
specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Piccolo was written at the Human-Computer Interaction Laboratory www.cs.umd.edu/hcil
by Jesse Grosjean
* under the supervision of Ben Bederson. The Piccolo website is www.cs.umd.edu/hcil/piccolo
.
*/
package edu.umd.cs.piccolo;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Transparency;
import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.print.Book;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.awt.print.PrinterJob;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import javax.swing.event.EventListenerList;
import javax.swing.event.SwingPropertyChangeSupport;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import edu.umd.cs.piccolo.activities.PActivity;
import edu.umd.cs.piccolo.activities.PColorActivity;
import edu.umd.cs.piccolo.activities.PInterpolatingActivity;
import edu.umd.cs.piccolo.activities.PTransformActivity;
import edu.umd.cs.piccolo.event.PInputEventListener;
import edu.umd.cs.piccolo.util.PAffineTransform;
import edu.umd.cs.piccolo.util.PBounds;
import edu.umd.cs.piccolo.util.PNodeFilter;
import edu.umd.cs.piccolo.util.PObjectOutputStream;
import edu.umd.cs.piccolo.util.PPaintContext;
import edu.umd.cs.piccolo.util.PPickPath;
import edu.umd.cs.piccolo.util.PUtil;
/**
* <b>PNode</b> is the central abstraction in Piccolo. All objects
that are
* visible on the screen are instances of the node class. All nodes
may have
* other "child" nodes added to them.
* <p>
* See edu.umd.piccolo.examples.NodeExample.java for demonstrations
of how nodes
* can be used and how new types of nodes can be created.
* <P>
* @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 {_at_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
* {_at_link #getBounds getBounds}, {_at_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 {_at_link #getFullBounds getFullBounds}, {_at_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
* {_at_link #getTransform getTransform}, {_at_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
* {_at_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
* {_at_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 {_at_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 {_at_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 {_at_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 {_at_link #getChildrenReference getChildrenReference}, {_at_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 {_at_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;
/**
* <b>PSceneGraphDelegate</b> 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.
* <P>
* 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();
}
return clientProperties;
}
/**
* Returns the value of the client attribute with the specified key.
Only
* attributes added with <code>addAttribute</code> 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 {
return clientProperties.getAttribute(key);
}
}
/**
* Add an arbitrary key/value to this node.
* <p>
* The <code>get/add attribute<code> 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.
* <p>
* 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);
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);
}
}
/**
* 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
public Object getAttribute(Object key, Object def) {
Object o = getAttribute(key);
return (o == null ? def : o);
}
public boolean getBooleanAttribute(Object key, boolean def) {
Boolean b = (Boolean)getAttribute(key);
return (b == null ? def : b.booleanValue());
}
public int getIntegerAttribute(Object key, int def) {
Number n = (Number)getAttribute(key);
return (n == null ? def : n.intValue());
}
public double getDoubleAttribute(Object key, double def) {
Number n = (Number)getAttribute(key);
return (n == null ? def : n.doubleValue());
}
/**
* @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) {
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();
}
};
}
//****************************************************************
// 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.
//***************************************************************
/**
* 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 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 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 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 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 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
* 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
* 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
* 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 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 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 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;
}
/**
* 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.
//****************************************************************
/**
* 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);
}
}
/**
* 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.
//****************************************************************
/**
* 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();
}
/**
* 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
<code>setBounds</code>.
* When you are done call <code>endResizeBounds</code> 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() {
}
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());
}
/**
* 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);
if (width <= 0 || height <= 0) {
bounds.reset();
}
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;
}
/**
* 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);
}
/**
* Return the x position (in local coords) of this node's bounds.
*/
public double getX() {
return getBoundsReference().getX();
}
/**
* 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();
}
/**
* 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);
}
/**
* 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 <code>fullIntersects</
code> 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);
}
//****************************************************************
// 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.
//****************************************************************
/**
* 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();
}
/**
* 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;
}
/**
* 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;
}
/**
* 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.
//****************************************************************
/**
* 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;
}
/**
* 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 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.
* <p>
* For example, If you have two nodes, A and B, and you call
* <PRE>
* 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);
* </PRE>
* 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 <code>paint</code> or
* <code>paintAfterChildren</code> 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);
paintContext.popTransparency(transparency);
paintContext.popTransform(transform);
}
}
/**
* 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) {
}
/**
* 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 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();
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);
}
GraphicsConfiguration graphicsConfiguration =
GraphicsEnvironment
.getLocalGraphicsEnvironment
().getDefaultScreenDevice().getDefaultConfiguration();
BufferedImage result =
graphicsConfiguration.createCompatibleImage(width, height,
Transparency.TRANSLUCENT);
return toImage(result, backGroundPaint);
}
/**
* 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();
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 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);
if (printJob.printDialog()) {
try {
printJob.print();
} catch (Exception e) {
System.out.println("Error Printing");
e.printStackTrace();
}
}
}
/**
* 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;
}
Graphics2D g2 = (Graphics2D)graphics;
PBounds imageBounds = getFullBounds();
imageBounds.expandNearestIntegerDimensions();
g2.setClip(0, 0, (int)pageFormat.getWidth(),
(int)pageFormat.getHeight());
g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
// 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);
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 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 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 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;
}
/**
* 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);
}
}
/**
* 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
<code>pick</code> or
* <code>pickAfterChildren</code>.
*
* @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 (thisPickable) {
if (pickAfterChildren(pickPath)) {
return true;
}
}
pickPath.popTransform(transform);
pickPath.popNode(this);
}
return false;
}
public void findIntersectingNodes(Rectangle2D fullBounds, ArrayList
results) {
if (fullIntersects(fullBounds)) {
Rectangle2D localBounds =
parentToLocal((Rectangle2D)fullBounds.clone());
if (intersects(localBounds)) {
results.add(this);
}
int count = getChildrenCount();
for (int i = count - 1; i >= 0; i--) {
PNode each = (PNode) children.get(i);
each.findIntersectingNodes(localBounds, results);
}
}
}
/**
* 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);
}
/**
* 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();
if (oldParent != null) {
oldParent.removeChild(child);
}
child.setParent(this);
getChildrenReference().add(index, child);
child.invalidatePaint();
invalidateFullBounds();
firePropertyChange(PROPERTY_CODE_CHILDREN, PROPERTY_CHILDREN, null,
children);
}
/**
* 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);
}
}
/**
* 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 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;
}
/**
* 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);
}
}
/**
* 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);
}
}
/**
* 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 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;
}
/**
* 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));
}
/**
* 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();
firePropertyChange(PROPERTY_CODE_CHILDREN, PROPERTY_CHILDREN, null,
children);
return child;
}
/**
* 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);
}
}
/**
* 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);
}
}
/**
* 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 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;
}
/**
* 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);
}
}
return results;
}
//****************************************************************
// 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);
}
/**
* 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
//****************************************************************
/**
* 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 <code>null</code>.
*
* @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 (fullBoundsInvalid) {
result.append(",fullBoundsInvalid");
}
if (pickable) {
result.append(",pickable");
}
if (childrenPickable) {
result.append(",childrenPickable");
}
if (visible) {
result.append(",visible");
}
return result.toString();
}
}