// PathVisio,
// a tool for data visualization and analysis using Biological Pathways
// Copyright 2006-2011 BiGCaT Bioinformatics
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package org.pathvisio.core.model;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.bridgedb.DataSource;
import org.bridgedb.Xref;
import org.jdom.Document;
import org.pathvisio.core.biopax.BiopaxReferenceManager;
import org.pathvisio.core.model.GraphLink.GraphIdContainer;
import org.pathvisio.core.model.GraphLink.GraphRefContainer;
import org.pathvisio.core.preferences.GlobalPreference;
import org.pathvisio.core.preferences.PreferenceManager;
import org.pathvisio.core.util.Utils;
/**
* PathwayElement is responsible for maintaining the data for all the individual
* objects that can appear on a pwy (Lines, GeneProducts, Shapes, etc.)
*
* All PathwayElements have an ObjectType. This ObjectType is specified at creation
* time and can't be modified. To create a PathwayElement,
* use the createPathwayElement() function. This is a factory method
* that returns a different implementation class depending on
* the specified ObjectType.
*
* PathwayElements have a number of properties which consist of a
* key, value pair.
*
* There are two types of properties: Static and Dynamic
* Static properties are one of the properties
*
* Dynamic properties can have any String as key. Their value is
* always of type String. Dynamic properties are not essential for
* the functioning of PathVisio and can be used to
* store arbitrary data. In GPML, dynamic properties are
* stored in an tag.
* Internally, dynamic properties are stored in a Map
*
* Static properties must have a key from the StaticProperty enum
* Their value can be various types which can be
* obtained from StaticProperty.type(). Static properties can
* be queried with getStaticProperty (key) and
* setStaticProperty(key, value), but also specific accessors
* such as e.g. getTextLabel() and setTextLabel()
*
* Internally, dynamic properties are stored in various
* fields of the PathwayElement Object.
* The static properties are a union of all possible fields
* (e.g it has both start and endpoints for lines,
* and label text for labels)
*
* the setPropertyEx() and getPropertyEx() functions can be used
* to access both dynamic and static properties
* from the same function. If key instanceof String then it's
* assumed the caller wants a dynamic
* property, if key instanceof StaticProperty then the static property
* is used.
*
* most static properties cannot be set to null. Notable exceptions are graphId,
* startGraphRef and endGraphRef.
*/
public class PathwayElement implements GraphIdContainer, Comparable
{
//TreeMap has better performance than HashMap
//in the (common) case where no attributes are present
//This map should never contain non-null values, if a value
//is set to null the key should be removed.
private Map attributes = new TreeMap();
/**
* get a set of all dynamic property keys
*/
public Set getDynamicPropertyKeys()
{
return attributes.keySet();
}
/**
* set a dynamic property.
* Setting to null means removing this dynamic property altogether
*/
public void setDynamicProperty (String key, String value)
{
if (value == null)
attributes.remove(key);
else
attributes.put (key, value);
fireObjectModifiedEvent(PathwayElementEvent.createSinglePropertyEvent(this, key));
}
/**
* get a dynamic property
*/
public String getDynamicProperty (String key)
{
return attributes.get (key);
}
/**
* A comment in a pathway element: each
* element can have zero or more comments with it, and
* each comment has a source and a text.
*/
public class Comment implements Cloneable
{
public Comment(String aComment, String aSource)
{
source = aSource;
comment = aComment;
}
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
private String source;
private String comment;
public String getSource() { return source; }
public String getComment() { return comment; }
public void setSource(String s) {
if(s != null && !source.equals(s)) {
source = s;
changed();
}
}
public void setComment(String c) {
if(c != null && !comment.equals(c)) {
comment = c;
changed();
}
}
private void changed() {
fireObjectModifiedEvent(PathwayElementEvent.createSinglePropertyEvent(PathwayElement.this, StaticProperty.COMMENTS));
}
public String toString() {
String src = "";
if(source != null && !"".equals(source)) {
src = " (" + source + ")";
}
return comment + src;
}
}
/**
* Represents a generic point in an coordinates.length dimensional space.
* The point is automatically a {@link GraphIdContainer} and therefore lines
* can link to the point.
* @see MPoint
* @see MAnchor
* @author thomas
*
*/
private abstract class GenericPoint implements Cloneable, GraphIdContainer
{
private double[] coordinates;
private String graphId;
GenericPoint(double[] coordinates)
{
this.coordinates = coordinates;
}
GenericPoint(GenericPoint p)
{
coordinates = new double[p.coordinates.length];
System.arraycopy(p.coordinates, 0, coordinates, 0, coordinates.length);
if (p.graphId != null)
graphId = p.graphId;
}
protected void moveBy(double[] delta)
{
for(int i = 0; i < coordinates.length; i++) {
coordinates[i] += delta[i];
}
fireObjectModifiedEvent(PathwayElementEvent.createCoordinatePropertyEvent(PathwayElement.this));
}
protected void moveTo(double[] coordinates)
{
this.coordinates = coordinates;
fireObjectModifiedEvent(PathwayElementEvent.createCoordinatePropertyEvent(PathwayElement.this));
}
protected void moveTo(GenericPoint p)
{
coordinates = p.coordinates;
fireObjectModifiedEvent(PathwayElementEvent.createCoordinatePropertyEvent(PathwayElement.this));;
}
protected double getCoordinate(int i) {
return coordinates[i];
}
public String getGraphId()
{
return graphId;
}
public String setGeneratedGraphId()
{
setGraphId(parent.getUniqueGraphId());
return graphId;
}
public void setGraphId(String v)
{
GraphLink.setGraphId(v, this, PathwayElement.this.parent);
graphId = v;
fireObjectModifiedEvent(PathwayElementEvent.createSinglePropertyEvent(PathwayElement.this, StaticProperty.GRAPHID));
}
public Object clone() throws CloneNotSupportedException
{
GenericPoint p = (GenericPoint) super.clone();
if (graphId != null)
p.graphId = graphId;
return p;
}
public Set getReferences()
{
return GraphLink.getReferences(this, parent);
}
public Pathway getPathway() {
return parent;
}
public PathwayElement getParent()
{
return PathwayElement.this;
}
}
/**
* This class represents the Line.Graphics.Point element in GPML.
* @author thomas
*
*/
public class MPoint extends GenericPoint implements GraphRefContainer
{
private String graphRef;
private boolean relativeSet;
public MPoint(double x, double y)
{
super(new double[] { x, y, 0, 0 });
}
MPoint(MPoint p)
{
super(p);
if (p.graphRef != null)
graphRef = p.graphRef;
}
public void moveBy(double dx, double dy)
{
super.moveBy(new double[] { dx, dy, 0, 0 });
}
public void moveTo(double x, double y)
{
super.moveTo(new double[] { x, y, 0, 0 });
}
public void setX(double nx)
{
if (nx != getX())
moveBy(nx - getX(), 0);
}
public void setY(double ny)
{
if (ny != getY())
moveBy(0, ny - getY());
}
public double getX()
{
if(isRelative()) {
return getAbsolute().getX();
} else {
return getCoordinate(0);
}
}
public double getY()
{
if(isRelative()) {
return getAbsolute().getY();
} else {
return getCoordinate(1);
}
}
protected double getRawX() {
return getCoordinate(0);
}
protected double getRawY() {
return getCoordinate(1);
}
public double getRelX() {
return getCoordinate(2);
}
public double getRelY() {
return getCoordinate(3);
}
private Point2D getAbsolute() {
return getGraphIdContainer().toAbsoluteCoordinate(
new Point2D.Double(getRelX(), getRelY())
);
}
public void setRelativePosition(double rx, double ry) {
moveTo(new double[] { getX(), getY(), rx, ry });
relativeSet = true;
}
/**
* Checks if the position of this point should be stored
* as relative or absolute coordinates
* @return true if the coordinates are relative, false if not
*/
public boolean isRelative() {
Pathway p = getPathway();
if(p != null) {
GraphIdContainer gc = getPathway().getGraphIdContainer(graphRef);
return gc != null;
}
return false;
}
/**
* Helper method for converting older GPML files without
* relative coordinates.
* @return true if {@link #setRelativePosition(double, double)} was called to
* set the relative coordinates, false if not.
*/
protected boolean relativeSet() {
return relativeSet;
}
private GraphIdContainer getGraphIdContainer() {
return getPathway().getGraphIdContainer(graphRef);
}
public String getGraphRef()
{
return graphRef;
}
/**
* Set a reference to another object with a graphId. If a parent is set,
* this will automatically deregister the previously held reference and
* register the new reference as necessary
*
* @param v
* reference to set.
*/
public void setGraphRef(String v)
{
if (!Utils.stringEquals(graphRef, v))
{
if (parent != null)
{
if (graphRef != null)
{
parent.removeGraphRef(graphRef, this);
}
if (v != null)
{
parent.addGraphRef(v, this);
}
}
graphRef = v;
}
}
public Object clone() throws CloneNotSupportedException
{
MPoint p = (MPoint) super.clone();
if (graphRef != null)
p.graphRef = graphRef;
return p;
}
public Point2D toPoint2D() {
return new Point2D.Double(getX(), getY());
}
/**
* Link to an object. Current absolute coordinates
* will be converted to relative coordinates based on the
* object to link to.
*/
public void linkTo(GraphIdContainer idc) {
Point2D rel = idc.toRelativeCoordinate(toPoint2D());
linkTo(idc, rel.getX(), rel.getY());
}
/**
* Link to an object using the given relative coordinates
*/
public void linkTo(GraphIdContainer idc, double relX, double relY) {
String id = idc.getGraphId();
if(id == null) id = idc.setGeneratedGraphId();
setGraphRef(idc.getGraphId());
setRelativePosition(relX, relY);
}
/** note that this may be called any number of times when this point is already unlinked */
public void unlink()
{
if (graphRef != null)
{
if (getPathway() != null)
{
Point2D abs = getAbsolute();
moveTo(abs.getX(), abs.getY());
}
relativeSet = false;
setGraphRef(null);
fireObjectModifiedEvent(PathwayElementEvent.createCoordinatePropertyEvent(PathwayElement.this));
}
}
public Point2D toAbsoluteCoordinate(Point2D p) {
return new Point2D.Double(p.getX() + getX(), p.getY() + getY());
}
public Point2D toRelativeCoordinate(Point2D p) {
return new Point2D.Double(p.getX() - getX(), p.getY() - getY());
}
/**
* Find out if this point is linked to an object.
* Returns true if a graphRef exists and is not an empty string
*/
public boolean isLinked() {
String ref = getGraphRef();
return ref != null && !"".equals(ref);
}
public void refeeChanged()
{
// called whenever the object being referred to has changed.
fireObjectModifiedEvent(PathwayElementEvent.createCoordinatePropertyEvent(PathwayElement.this));
}
}
/**
* This class represents the Line.Graphics.Anchor element in GPML
* @author thomas
*
*/
public class MAnchor extends GenericPoint {
AnchorType shape = AnchorType.NONE;
public MAnchor(double position) {
super(new double[] { position });
}
public MAnchor(MAnchor a) {
super(a);
shape = a.shape;
}
public void setShape(AnchorType type) {
if(!this.shape.equals(type) && type != null) {
this.shape = type;
fireObjectModifiedEvent(PathwayElementEvent.createSinglePropertyEvent(PathwayElement.this, StaticProperty.LINESTYLE));
}
}
public AnchorType getShape() {
return shape;
}
public double getPosition() {
return getCoordinate(0);
}
public void setPosition(double position) {
if(position != getPosition()) {
moveBy(position - getPosition());
}
}
public void moveBy(double delta) {
super.moveBy(new double[] { delta });
}
public Point2D toAbsoluteCoordinate(Point2D p) {
Point2D l = ((MLine)getParent()).getConnectorShape().fromLineCoordinate(getPosition());
return new Point2D.Double(p.getX() + l.getX(), p.getY() + l.getY());
}
public Point2D toRelativeCoordinate(Point2D p) {
Point2D l = ((MLine)getParent()).getConnectorShape().fromLineCoordinate(getPosition());
return new Point2D.Double(p.getX() - l.getX(), p.getY() - l.getY());
}
}
/* Some default values */
private static final int M_INITIAL_FONTSIZE = 10;
private static final int M_INITIAL_LABEL_WIDTH = 80;
private static final int M_INITIAL_LABEL_HEIGHT = 20;
private static final int M_INITIAL_LINE_LENGTH = 30;
private static final int M_INITIAL_STATE_SIZE = 15;
private static final int M_INITIAL_SHAPE_SIZE = 30;
private static final int M_INITIAL_CELLCOMP_HEIGHT = 100;
private static final int M_INITIAL_CELLCOMP_WIDTH = 200;
private static final int M_INITIAL_BRACE_HEIGHT = 15;
private static final int M_INITIAL_BRACE_WIDTH = 60;
private static final int M_INITIAL_GENEPRODUCT_WIDTH = 80;
private static final int M_INITIAL_GENEPRODUCT_HEIGHT = 20;
// groups should be behind other graphics
// to allow background colors
private static final int Z_ORDER_GROUP = 0x1000;
// default order of geneproduct, label, shape and line determined
// by GenMAPP legacy
private static final int Z_ORDER_GENEPRODUCT = 0x8000;
private static final int Z_ORDER_LABEL = 0x7000;
private static final int Z_ORDER_SHAPE = 0x4000;
private static final int Z_ORDER_LINE = 0x3000;
// default order of uninteresting elements.
private static final int Z_ORDER_DEFAULT = 0x0000;
/**
* default z order for newly created objects
*/
private static int getDefaultZOrder(ObjectType value)
{
switch (value)
{
case SHAPE:
return Z_ORDER_SHAPE;
case STATE:
return Z_ORDER_GENEPRODUCT + 10;
case DATANODE:
return Z_ORDER_GENEPRODUCT;
case LABEL:
return Z_ORDER_LABEL;
case LINE:
return Z_ORDER_LINE;
case LEGEND:
case INFOBOX:
case MAPPINFO:
case BIOPAX:
return Z_ORDER_DEFAULT;
case GROUP:
return Z_ORDER_GROUP;
default:
throw new IllegalArgumentException("Invalid object type " + value);
}
}
/**
* Instantiate a pathway element.
* The required parameter objectType ensures only objects with a valid type
* can be created.
*
* @param ot
* Type of object, one of the ObjectType.* fields
*/
public static PathwayElement createPathwayElement(ObjectType ot) {
PathwayElement e;
switch (ot) {
case GROUP:
e = new MGroup();
break;
case LINE:
e = new MLine();
break;
case STATE:
e = new MState();
break;
default:
e = new PathwayElement(ot);
break;
}
return e;
}
protected PathwayElement(ObjectType ot)
{
/* set default value for transparency */
if (ot == ObjectType.LINE || ot == ObjectType.LABEL || ot == ObjectType.DATANODE || ot == ObjectType.STATE)
{
fillColor = Color.WHITE;
}
else
{
fillColor = null;
}
/* set default value for shapeType */
if (ot == ObjectType.LABEL)
{
shapeType = ShapeType.NONE;
}
else
{
shapeType = ShapeType.RECTANGLE;
}
objectType = ot;
zOrder = getDefaultZOrder (ot);
}
int zOrder;
public int getZOrder()
{
return zOrder;
}
public void setZOrder(int z) {
if(z != zOrder) {
zOrder = z;
fireObjectModifiedEvent(PathwayElementEvent.createSinglePropertyEvent(this, StaticProperty.ZORDER));
}
}
/**
* Parent of this object: may be null (for example, when object is in
* clipboard)
*/
protected Pathway parent = null;
public Pathway getParent()
{
return parent;
}
/**
* Get the parent pathway. Same as {@link #getParent()}, but necessary to
* comply to the {@link GraphIdContainer} interface.
*/
public Pathway getPathway() {
return parent;
}
/**
* Set parent. Do not use this method directly! parent is set automatically
* when using Pathway.add/remove
* @param v the parent
*/
void setParent(Pathway v)
{
parent = v;
}
/**
* Returns keys of available static properties and dynamic properties as an object list
*/
public Set