/*
* $Id: Layer.java,v 1.25 2003/09/03 22:50:51 dwd Exp $
*
* This software is provided by NOAA for full, free and open release. It is
* understood by the recipient/user that NOAA assumes no liability for any
* errors contained in the code. Although this software is released without
* conditions or restrictions in its use, it is expected that appropriate
* credit be given to its author and to the National Oceanic and Atmospheric
* Administration should the software be included by the recipient as an
* element in other product development.
*/
package gov.noaa.pmel.sgt;
import gov.noaa.pmel.sgt.dm.Collection;
import gov.noaa.pmel.sgt.dm.SGTData;
import gov.noaa.pmel.sgt.swing.Draggable;
import gov.noaa.pmel.util.Dimension2D;
import gov.noaa.pmel.util.Rectangle2D;
import gov.noaa.pmel.util.Debug;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Iterator;
import java.awt.*;
// jdk1.2
//import java.awt.geom.Rectangle2D;
/**
* A Layer
contains a single Graph
object
* and multiple LayerChild
objects.
* There can be many Layer
objects associated with each
* Pane
object and
* the Layer
objects can share Transform
* and Axis
objects, but are not
* required to. The Layer
is also where keys
* related to Color
, Vectors, and
* Lines are attached. The can be at most one key of each type attached to a
* Layer
.
*
* The Layer
object transforms physical coordinates
* to device coordinates. All objects that attach to a
* Layer
use physical coordinates.
* The exception to this is the Graph
object
* (and its children), since these objects transform user
* coordinates to physical coordinates.
*
* The following is a simple example of using the Pane
,
* Layer
, and SGLabel
objects
* together. In this example, the Pane
and
* Layer
objects are created such that,
* in the absence of any resizing, 100 pixels is equal to 1.0
* physical units. Two labels are created, the first contains
* the current time and is located in the bottom left of
* the Layer
. The second label is a title that is
* positioned near the top and centered.
*
* Pane pane; * Layer layer; * SGLabel title; * SGLabel label; * GeoDate stime; * ... * // * // Instantiate Pane, Layer, and GeoDate objects. * // * pane = new Pane("test pane", new Dimension(400, 300)); * pane.setLayout(new StackedLayout()); * layer = new Layer("Test Layer", new Dimension2D(4.0, 3.0)); * stime = new GeoDate(); * // * // Instatiate an SGLabel object as label, set its text to the * // current time and position it near the lower-left corner * // of the layer. * // * label = new SGLabel("test", stime.toString(), new Point2D.Double(0.05, 0.05)); * // * // Set properties for label. * // * label.setAlign(SGLabel.BOTTOM, SGLabel.LEFT); * label.setColor(Color.magenta); * label.setHeightP(0.15); * label.setFont(new Font("Dialog", Font.PLAIN, 10)); * // * // Add label to layer. * // * layer.addChild(label); * // * // Instatiate an SGLabel object as title, set its text and position * // it near the top of the layer and centered. Set the properties * // for title. * // * title = new SGLabel("title", "SciGraph Test!", new Point2D.Double(2.125, 2.9)); * title.setAlign(SGLabel.TOP, SGLabel.CENTER); * title.setHeightP(0.25); * title.setFont(new Font("Helvetica", Font.BOLD, 14)); * // * // Add title to layer and add layer to pane. * // * layer.addChild(title); * pane.add(layer); ** * @author Donald Denbo * @version $Revision: 1.25 $, $Date: 2003/09/03 22:50:51 $ * @since 1.0 * @see Pane * @see Graph * @see ColorKey * @see SGLabel * @see LineKey * @see gov.noaa.pmel.util.GeoDate **/ public class Layer extends Component implements Cloneable, LayerControl { private String ident_; /**@shapeType AggregationLink * @clientCardinality 1 * @label graph*/ private Graph graph_; /**@shapeType AggregationLink @associates LayerChild @supplierCardinality 0..* * @undirected * @label children */ private Vector children_; private double pWidth_; private double pHeight_; private double ax_; private double ay_; private int xoff_; private int yoff_; private double xoff2_; private double yoff2_; protected AbstractPane pane_; private void computeScale() { Dimension d; boolean hasG2 = getGraphics() instanceof Graphics2D; // compute xoff and yoff as double then truncate to int Rectangle pbnds = pane_.getBounds(); Rectangle bnds = getBounds(); if(pane_.isPrinter()) { ax_ = 72; // java2 is in 1/72 of an inch ay_ = ax_; xoff2_ = (bnds.width - ax_*pWidth_)/2.0 + bnds.x; yoff2_ = bnds.height - (bnds.height - ay_*pHeight_)/2.0 + bnds.y; } else { // not printer ax_ = (double)bnds.width/pWidth_; ay_ = (double)bnds.height/pHeight_; if(ax_ > ay_) { ax_ = ay_; } else if(ay_ > ax_) { ay_ = ax_; } xoff2_ = (bnds.width - ax_*pWidth_)/2.0 + bnds.x - pbnds.x; yoff2_ = bnds.height - (bnds.height - ay_*pHeight_)/2.0 + bnds.y - pbnds.y; } xoff_ = (int)xoff2_; yoff_ = (int)yoff2_; if(Debug.DEBUG && pane_.isPrinter()) { System.out.println("Layer.computeScale["+getId()+"] printer = " + pane_.isPrinter()); System.out.println(" xd(min) = " + getXPtoD(0.0)); System.out.println(" xd(max) = " + getXPtoD(pWidth_)); System.out.println(" yd(min) = " + getYPtoD(0.0)); System.out.println(" yd(max) = " + getYPtoD(pHeight_)); } } /** * Set the size of the
Layer
in device units.
*
* @param sze dimension of the Layer
*/
public void setSize(Dimension sze) {
super.setSize(sze);
computeScale();
modified("Layer: setSize(Dimension)");
}
/**
* Set the size of the Layer
in device units.
*
* @param w width of the Layer
* @param h height of the Layer
*/
public void setSize(int w, int h) {
super.setSize(w, h);
computeScale();
modified("Layer: setSize(int,int)");
}
/**
* Set the location of the Layer
in device units.
*
* @param pt location of the Layer
*/
public void setLocation(Point pt) {
super.setLocation(pt);
computeScale();
modified("Layer: setLocation(Point)");
}
/**
* Set the location of the Layer
in device units.
*
* @param x horizontal location of the Layer
* @param y vertical location of the Layer
*/
public void setLocation(int x, int y) {
super.setLocation(x, y);
computeScale();
modified("Layer: setLocation(int,int)");
}
/**
* Set the bounds of the Layer
in device units.
*
* @param x horizontal location of the Layer
* @param y vertical location of the Layer
* @param w width of the Layer
* @param h height of the Layer
*/
public void setBounds(int x, int y, int w, int h) {
super.setBounds(x, y, w, h);
computeScale();
// System.out.println("Layer.setBounds(" + x + ", " + y + ", " +
// w + ", " + h + ")");
modified("Layer: setBounds(int,int,int,int)");
}
/**
* Set the bounds of the Layer
in device units.
*
* @param bnds bounds of the Layer
*/
public void setBounds(Rectangle bnds) {
super.setBounds(bnds);
computeScale();
modified("Layer: setBounds(Rectangle)");
}
/**
* Transform physical units to device for x coordinate.
*
* @param xp x physical coordinate
* @return x device coordinate
* @since 2.0
*/
public int getXPtoD(double xp) {
return (int)(ax_*xp) + xoff_;
}
/**
* Transform physcial units to device for y coordinate.
*
* @param yp y physical coordinate
* @return y device coordinate
* @since 2.0
*/
public int getYPtoD(double yp) {
return yoff_ - (int)(ay_*yp);
}
/**
* Transform physical units to device for x coordinate.
*
* @param xp x physical coordinate
* @return x device coordinate
* @since 3.0
*/
public double getXPtoD2(double xp) {
return ax_*xp + xoff2_;
}
/**
* Transform physcial units to device for y coordinate.
*
* @param yp y physical coordinate
* @return y device coordinate
* @since 3.0
*/
public double getYPtoD2(double yp) {
return yoff2_ - ay_*yp;
}
protected double getXSlope() {
return ax_;
}
protected double getYSlope() {
return ay_;
}
protected double getXOffset() {
return xoff2_;
}
protected double getYOffset() {
return yoff2_;
}
/**
* Transform device units to physical for the x direction.
*
* @param xd device x coordinate
*
* @return physical x coordinate
*/
public double getXDtoP(int xd) {
return (double)(xd - xoff2_)/ax_;
}
/**
* Transform device units to physical for the y direction.
*
* @param yd device y coordinate
*
* @return physical y coordinate
*/
public double getYDtoP(int yd) {
return (double)(yoff2_ - yd)/ay_;
}
/**
* Create a Layer
object.
* The Layer
is created with a default
* width and height equal to 1.0.
*
* @param id identifier for Layer
**/
public Layer(String id) {
this(id, new Dimension2D(1.0, 1.0));
}
/**
* Create a Layer
object.
* The Layer
is created with the specified
* dimensions and identifier.
*
* @param id identifier for Layer
* @param psize The physical dimensions of the Layer
**/
public Layer(String id,Dimension2D psize) {
ident_ = id;
pWidth_ = psize.width;
pHeight_ = psize.height;
children_ = new Vector(5,5);
}
/**
* Default constructor for Layer
.
* The Layer
is created with an
* empty identifier and a width and height equal to 1.0f.
**/
public Layer() {
this("");
}
/**
* Copy the Layer
and its attached classes.
*
* @return copy
*/
public Layer copy() {
Layer newLayer;
try {
newLayer = (Layer)clone();
} catch (CloneNotSupportedException e) {
newLayer = new Layer(ident_, new Dimension2D(pWidth_, pHeight_));
}
//
// copy children
//
newLayer.children_ = new Vector(5,5);
//
if(!children_.isEmpty()) {
LayerChild newChild;
for(Enumeration it = children_.elements(); it.hasMoreElements();) {
newChild = ((LayerChild)it.nextElement()).copy();
newLayer.addChild(newChild);
}
}
//
// copy Graph
//
if(graph_ != (Graph) null) {
Graph newGraph = graph_.copy();
newLayer.setGraph(newGraph);
}
return newLayer;
}
/**
* Draw the Layer and its attached classes.
*
* @param g graphics context
* @exception PaneNotFoundException if a pane object is not found
**/
public void draw(Graphics g) throws PaneNotFoundException {
if(pane_ == null) throw new PaneNotFoundException();
computeScale();
if(false) {
System.out.println("\nLayer.draw(g): " + ident_);
System.out.println(" layer.getBounds(" + ident_ + ") = " + getBounds());
System.out.println(" layer.getBoundsP(" + ident_ + ") = " + getBoundsP());
System.out.println(" pane.getBounds(" + pane_.getId() + ") = " + pane_.getBounds());
}
/* int x0, y0, x1, y1;
x0 = getXPtoD(0.0f);
y0 = getYPtoD(0.0f);
Rectangle2D.Double psize_ = getBoundsP();
x1 = getXPtoD(psize_.width);
y1 = getYPtoD(psize_.height);
g.setColor(Color.blue);
g.drawRect(x0,y1,x1-x0-1,y0-y1-1); */
// System.out.println("Layer.draw(g): " + ident_ + ", [" + ax_ + ", " + ay_ + "], [" +
// xoff2_ + ", " + yoff2_ + "]");
//
// draw Graph
//
if(graph_ != (Graph) null) graph_.draw(g);
//
// draw children
//
if(!children_.isEmpty()) {
LayerChild child;
for(Enumeration it = children_.elements(); it.hasMoreElements();) {
child = (LayerChild)it.nextElement();
if(!(child instanceof Draggable)) {
try {
child.draw(g);
} catch(LayerNotFoundException e) {
}
}
}
}
}
public void drawDraggableItems(Graphics g) throws PaneNotFoundException {
if(pane_ == null) throw new PaneNotFoundException();
//
// draw draggable items
//
if(!children_.isEmpty()) {
LayerChild child;
for(Enumeration it = children_.elements(); it.hasMoreElements();) {
child = (LayerChild)it.nextElement();
if(child instanceof Draggable) {
try {
child.draw(g);
} catch(LayerNotFoundException e) {
}
}
}
}
}
/**
* Associate a graph with the layer. Only one graph or its children may be attached to a
* layer. Multiple graphs are created by using multiple layers.
*
* @param gr graph
* @return True if attachment was succesful
* @see Graph
**/
public boolean setGraph(Graph gr) {
graph_ = gr;
graph_.setLayer(this);
modified("Layer: setGraph()");
return true;
}
/**
* Get the Graph
attached to the layer.
*
* @return Reference to the Graph
.
**/
public Graph getGraph() {
return graph_;
}
/**
* Add a LayerChild
to the Layer
.
* Each Layer
can contain as many children as needed.
*
* @param child A LayerChild
* @see SGLabel
* @see LineKey
* @see ColorKey
* @see Ruler
**/
public void addChild(LayerChild child) {
child.setLayer(this);
children_.addElement(child);
modified("Layer: addChild()");
}
/**
* Remove a LayerChild
object from the Layer
.
*
* @param child A ChildLayer
object associated with the Layer
* @exception ChildNotFoundException The child is not associated with the Layer
* @see SGLabel
* @see LineKey
* @see ColorKey
* @see Ruler
**/
public void removeChild(LayerChild child) throws ChildNotFoundException {
if(!children_.isEmpty()) {
LayerChild chld;
boolean found = false;
for(Enumeration it = children_.elements(); it.hasMoreElements();) {
chld = (LayerChild)it.nextElement();
if(chld.equals(child)) {
children_.removeElement(child);
found = true;
modified("Layer: removeChild(LayerChild)");
}
}
if(!found) throw new ChildNotFoundException();
} else {
throw new ChildNotFoundException();
}
}
/**
* Remove a LayerChild
object from the Layer
.
*
* @param labid An identifier for a LayerChild
associated with the Layer
* @exception ChildNotFoundException The child is not associated with the Layer
* @see SGLabel
* @see LineKey
* @see ColorKey
* @see Ruler
**/
public void removeChild(String labid) throws ChildNotFoundException {
if(!children_.isEmpty()) {
boolean found = false;
LayerChild child;
for(Enumeration it = children_.elements(); it.hasMoreElements();) {
child = (LayerChild)it.nextElement();
if(child.getId().equals(labid)) {
children_.removeElement(child);
found = true;
modified("Layer: removeChild(String)");
}
}
if(!found) throw new ChildNotFoundException();
} else {
throw new ChildNotFoundException();
}
}
/**
* Find LayerChild
in Layer
.
* @param id LayerChild identifier
* @return LayerChild
* @since 3.0
*/
public LayerChild findChild(String id) {
LayerChild child = null;
for(Enumeration it=children_.elements(); it.hasMoreElements();) {
child = (LayerChild)it.nextElement();
if(child.getId().equals(id)) return child;
}
return null;
}
/**
* Tests if a LayerChild
is attached to the
* Layer
.
*
* @param child LayerChild to test
* @return true if attached to Layer
* @since 2.0
*/
public boolean isChildAttached(LayerChild child) {
boolean found = false;
if(!children_.isEmpty()) {
LayerChild chld;
for(Enumeration it = children_.elements(); it.hasMoreElements();) {
chld = (LayerChild)it.nextElement();
if(chld.equals(child)) {
children_.removeElement(child);
found = true;
break;
}
}
}
return found;
}
/**
* Remove all LayerChild
objects from the Layer
.
*/
public void removeAllChildren() {
children_.removeAllElements();
modified("Layer: removeAllChildren()");
}
/**
* Get a child associated with the Layer
.
*
* @param labid A LayerChild
object identifier
* @return layerChild with id
* @exception ChildNotFoundException The child is not associated with the Layer
* @see SGLabel
* @see LineKey
* @see ColorKey
* @see Ruler
**/
public LayerChild getChild(String labid) throws ChildNotFoundException {
if(!children_.isEmpty()) {
LayerChild child;
for(Enumeration it = children_.elements(); it.hasMoreElements();) {
child = (LayerChild)it.nextElement();
if(child.getId() == labid) return child;
}
throw new ChildNotFoundException();
} else {
throw new ChildNotFoundException();
}
}
/**
* Create a Enumeration
for the
* LayerChild
's associated with the Layer
.
*
* @return Enumeration
for the LayerChild
objects.
* @see Enumeration
* @see SGLabel
* @see LineKey
* @see ColorKey
* @see Ruler
**/
public Enumeration childElements() {
return children_.elements();
}
/**
* @since 3.0
*/
public Iterator childIterator() {
return children_.iterator();
}
/**
* @since 3.0
*/
public LayerChild[] getChildren() {
LayerChild[] childs = new LayerChild[0];
childs = (LayerChild[])children_.toArray(childs);
return childs;
}
/**
* Set the size of the Layer
in physical coordinates.
*
* @param psize The physical size of the Layer
.
**/
public void setSizeP(Dimension2D psize) {
pWidth_ = psize.width;
pHeight_ = psize.height;
computeScale();
modified("Layer: setSizeP()");
}
/**
* Get the Layer
size in physical coordinates.
* This returns the physical coordinate size
* of the Layer
.
*
* @return A Dimension2D
containing the physical size of the Layer
.
* @see Dimension2D
**/
public Dimension2D getSizeP() {
return new Dimension2D(pWidth_, pHeight_);
}
/**
* Get the Layer
bounds in physical coordinates.
* The origin of the bounding rectangle,
* for a Layer
, is always (0,0).
*
* @return A Rectangle2D.Double
containing the physical bounds of the Layer
.
* @see java.awt.geom.Rectangle2D.Double
**/
public Rectangle2D.Double getBoundsP() {
return new Rectangle2D.Double(0.0, 0.0,
pWidth_, pHeight_);
}
/**
* Get the Layer
identifier.
*
* @return The identifier.
**/
public String getId() {
return ident_;
}
/**
* Set the Layer
identifier.
*
* @param id identifier
**/
public void setId(String id) {
ident_ = id;
}
/**
* Set the Pane
the Layer
is associated with.
* This method is called by Pane
when the
* Pane.add
method is exectued.
*
* @param p The Pane
**/
public void setPane(AbstractPane p) {
pane_ = p;
computeScale();
modified("Layer: setPane()");
}
/**
* Get the Pane
the Layer
is associated
* with.
*
* @return Refence to the Pane
**/
public AbstractPane getPane() {
return pane_;
}
/**
* Used internally by sgt.
* @param mess message
* @since 2.0
*/
public void modified(String mess) {
if(pane_ != null) {
// if(Debug.EVENT) System.out.println("Layer: modified(" + mess + ")");
pane_.setModified(true, mess);
}
}
/**
* Find object associated with a MOUSE_DOWN event. The getObjectAt method
* scans through all the objects associated with the layer to find one
* whose bounding box contains the mouse location.
*
* This method should not be called by a user.
*
* @param pt device coordinates
* @param check if true requires that object isSelectable
* @return object at location
**/
public Object getObjectAt(int x, int y, boolean check) {
return getObjectAt(new Point(x, y), check);
}
/**
* Find object associated with a MOUSE_DOWN event. The getObjectAt method
* scans through all the objects associated with the layer to find one
* whose bounding box contains the mouse location.
*
* This method should not be called by a user.
*
* @param pt device coordinates
* @return object at location
**/
public Object getObjectAt(int x,int y) {
return getObjectAt(new Point(x, y), true);
}
/**
* Find object associated with a MOUSE_DOWN event. The getObjectAt method
* scans through all the objects associated with the layer to find one
* whose bounding box contains the mouse location.
*
* This method should not be called by a user.
*
* @param pt device coordinates
* @param check if true requires that object isSelectable
* @return object at location
**/
public Object getObjectAt(Point pt, boolean check) {
Rectangle bnds;
Object obj;
if(!children_.isEmpty()) {
LayerChild child;
for(Enumeration it = children_.elements(); it.hasMoreElements();) {
child = (LayerChild)it.nextElement();
bnds = child.getBounds();
if(bnds.contains(pt) && (!check || child.isSelectable()) &&
child.isVisible()) {
if(child instanceof LineKey) {
return ((LineKey)child).getObjectAt(pt);
} else if(child instanceof PointCollectionKey) {
return ((PointCollectionKey)child).getObjectAt(pt);
} else if(child instanceof VectorKey) {
return ((VectorKey)child).getObjectAt(pt);
} else {
return child;
}
}
}
}
if(graph_ != null) {
obj = graph_.getObjectAt(pt);
if(obj != null) return obj;
}
return (Object) null;
}
/**
* Find objects associated with a MOUSE_DOWN event. The getObjecstAt method
* scans through all the objects associated with the layer to find those
* whose bounding box contains the mouse location.
*
* This method should not be called by a user.
*
* @param x mouse coordinate
* @param y mouse coordinate
* @param check if selectable
* @return object array
* @since 3.0
*/
public Object[] getObjectsAt(int x, int y, boolean check) {
Point pt = new Point(x, y);
Vector obList = new Vector();
Object obj = null;
Rectangle bnds;
if(!children_.isEmpty()) {
LayerChild child;
for(Enumeration it = children_.elements(); it.hasMoreElements();) {
child = (LayerChild)it.nextElement();
bnds = child.getBounds();
if(bnds.contains(pt) && (!check || child.isSelectable()) &&
child.isVisible()) {
if(child instanceof LineKey) {
obj = ((LineKey)child).getObjectAt(pt);
if(obj != null) obList.add(obj);
} else if(child instanceof PointCollectionKey) {
obj = ((PointCollectionKey)child).getObjectAt(pt);
if(obj != null) obList.add(obj);
} else if(child instanceof VectorKey) {
obj = ((VectorKey)child).getObjectAt(pt);
if(obj != null) obList.add(obj);
} else {
if(child != null) obList.add(child);
}
}
}
}
if(graph_ != null) {
obj = graph_.getObjectAt(pt);
if(obj != null) obList.add(obj);
}
return obList.toArray();
}
/**
* Get a String
representation of the
* Layer
.
*
* @return String
representation
*/
public String toString() {
String name = getClass().getName();
return name.substring(name.lastIndexOf(".")+1) + ": " + ident_;
}
/**
* Checks to see if a data id matches that data attached to the
* Graph
.
* @param id data identifier
* @return true if data is in layer
* @since 2.0
*/
public boolean isDataInLayer(String id) {
if(graph_ instanceof CartesianGraph) {
CartesianRenderer cr = ((CartesianGraph)graph_).getRenderer();
if(cr instanceof LineCartesianRenderer) {
if(((LineCartesianRenderer)cr).hasCollection()) {
Collection co = ((LineCartesianRenderer)cr).getCollection();
for(Enumeration it = co.elements(); it.hasMoreElements();) {
if(((SGTData)it.nextElement()).getId().equals(id)) return true;
}
} else {
return ((LineCartesianRenderer)cr).getLine().getId().equals(id);
}
} else if(cr instanceof GridCartesianRenderer) {
return ((GridCartesianRenderer)cr).getGrid().getId().equals(id);
} else if(cr instanceof PointCartesianRenderer) {
if(((PointCartesianRenderer)cr).hasCollection()) {
Collection co = ((PointCartesianRenderer)cr).getCollection();
for(Enumeration it = co.elements(); it.hasMoreElements();) {
if(((SGTData)it.nextElement()).getId().equals(id)) return true;
}
} else {
return ((PointCartesianRenderer)cr).getPoint().getId().equals(id);
}
}
}
return false;
}
}