/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 1999-2003, by :
* Corporate:
* Astrium SAS
* EADS CRC
* Individual:
* Claude Cazenave
* Nicolas Brodu
*
*
* $Id: ConnectionShape.java,v 1.16 2009/01/08 15:37:13 ogor Exp $
*
* Changes
* -------
*
*/
package jsynoptic.builtin;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import javax.swing.undo.CompoundEdit;
import jsynoptic.builtin.ui.ConnectionPropertiesPanel;
import simtools.diagram.DiagramSelection;
import simtools.diagram.ShapePointsSelection;
import simtools.diagram.Resizable;
import simtools.diagram.ElementSelection;
import simtools.diagram.TranslatableShapePointsInterface;
import simtools.diagram.gate.Connection;
import simtools.diagram.gate.ConnectionPathEdit;
import simtools.diagram.gate.Path;
import simtools.diagram.gate.ConnectionPathSelection;
import simtools.diagram.gate.Gate;
import simtools.diagram.gate.RightAnglePath;
import simtools.diagram.gate.StraightPath;
import simtools.shapes.AbstractShape;
import simtools.ui.JPropertiesPanel;
import simtools.ui.MenuResourceBundle;
import simtools.ui.ResourceFinder;
/**
* A Connection is a list of segments
*
* @author Claude Cazenave
*/
public class ConnectionShape extends Abstract1DShape implements
Connection,
Resizable,
TranslatableShapePointsInterface,
Serializable {
public static MenuResourceBundle resources = ResourceFinder.getMenu(ConnectionShape.class);
static final long serialVersionUID = -961863327687064518L;
static Polygon arrow = new Polygon(new int[] {10,0,10},new int[] {5,0,-5},3);
/**
* x vertices
* @deprecated see {@link Path} instead
*/
protected double[] _xv;
/**
* y vertices
* @deprecated see {@link Path} instead
*/
protected double[] _yv;
/**
* Display an arrow on last extremity
* @deprecated
*/
protected boolean displayLastArrow;
/**
* Display an arrow on first extremity
* @deprecated
*/
protected boolean displayFirstArrow;
/**
* First arrow transfrom used to draw the arrow polygon
*/
protected AffineTransform firstArrowTransform;
/**
* Last arrow transfrom used to draw the arrow polygon
*/
protected AffineTransform lastArrowTransform;
/**
* The gate attached to the first bound
*/
protected Gate fisrtGate = null;
/**
* The gate attached to the last bound
*/
protected Gate lastGate = null;
/**
* The connection path.
* Manage the connection nodes
*/
protected Path path;
/**
* Constructs a new Link instance
*/
public ConnectionShape(int x, int y, int w, int h) {
this(x, y, w, h, null, null);
}
/**
* Constructs a new Link instance
*/
public ConnectionShape(int x, int y, int w, int h, Gate fisrtGate, Gate lastGate) {
super(x, y, w, h);
//Gates
this.fisrtGate = fisrtGate;
this.lastGate = lastGate;
// Path
setPath( new RightAnglePath(new Point(x, y), new Point(x+w, y+h)) );
// Arrows
firstArrowTransform = null;
lastArrowTransform = null;
}
/**
* For background compatibility.
* Create a connection with a Straight Path from a list of nodes
* Constructs a new Link instance
*/
public ConnectionShape(double[] vx, double[] vy) {
super(0, 0, 100, 100);
//Gates
this.fisrtGate = null;
this.lastGate = null;
// Path
setPath( new StraightPath(vx, vy));
// Arrows
firstArrowTransform = null;
lastArrowTransform = null;
}
/* (non-Javadoc)
* @see simtools.shapes.AbstractShape#cloneShape()
*/
protected AbstractShape cloneShape() {
ConnectionShape cs = (ConnectionShape)super.cloneShape();
// clone the path
cs.path = path.clonePath();
// clone arrows
if(firstArrowTransform!=null){
cs.firstArrowTransform=(AffineTransform)firstArrowTransform.clone();
}
if(lastArrowTransform!=null){
cs.lastArrowTransform=(AffineTransform)lastArrowTransform.clone();
}
// Cloned connection is not connected to the original connection gates
cs.fisrtGate = null;
cs.lastGate = null;
return cs;
}
/* (non-Javadoc)
* @see jsynoptic.builtin.Abstract2DShape#getPanel(java.util.List)
*/
public JPropertiesPanel getPanel(List properties) {
if (properties == null){
return null;
}
if (properties.containsAll(Arrays.asList(new ConnectionShapePropertiesNames().getPropertyNames()))){
return new ConnectionPropertiesPanel(Builtin.resources.getString("Connection"));
} else {
return super.getPanel(properties);
}
}
/* (non-Javadoc)
* @see jsynoptic.builtin.Abstract1DShape#getCollectiveActions(simtools.diagram.DiagramSelection, double, double, java.lang.Object, int)
*/
public LinkedHashMap getCollectiveActions(DiagramSelection sel, double x, double y, Object o, int context) {
LinkedHashMap res = super.getCollectiveActions(sel, x, y, o, context);
res.put(resources.getString("RightAnglePath"), resources.getIcon("RightAnglePathIcon"));
res.put(resources.getString("StraightPath"), resources.getIcon("StraightPathIcon"));
return res;
}
/* (non-Javadoc)
* @see jsynoptic.builtin.Abstract1DShape#doAction(double, double, java.lang.Object, java.lang.String, javax.swing.undo.CompoundEdit)
*/
public boolean doAction(double x, double y, Object o, String action, CompoundEdit undoableEdit) {
boolean res = false;
if (action.equals(resources.getString("RightAnglePath"))) {
if (path instanceof StraightPath){
// Save old params
Rectangle dirtyArea = getBounds();
ConnectionPathEdit ce = new ConnectionPathEdit(this);
setPath(new RightAnglePath(path.getNode(0), path.getNode(path.getNodeNumber()-1)));
ce.pathHasChanged();
dirtyArea.add(getBounds());
dirtyArea.x -=10;
dirtyArea.y -=10;
dirtyArea.width +=20;
dirtyArea.height +=20;
notifyChange(dirtyArea);
res = true;
if (undoableEdit != null) {
undoableEdit.addEdit(ce);
}
}
} else if (action.equals(resources.getString("StraightPath"))) {
if (path instanceof RightAnglePath){
// Save old params
Rectangle dirtyArea = getBounds();
ConnectionPathEdit ce = new ConnectionPathEdit(this);
setPath(new StraightPath(path.getNode(0), path.getNode(path.getNodeNumber()-1)));
ce.pathHasChanged();
dirtyArea.add(getBounds());
dirtyArea.x -=10;
dirtyArea.y -=10;
dirtyArea.width +=20;
dirtyArea.height +=20;
notifyChange(dirtyArea);
res = true;
if (undoableEdit != null) {
undoableEdit.addEdit(ce);
}
}
} else {
res = super.doAction(x, y, o, action, undoableEdit);
}
return res;
}
/* (non-Javadoc)
* @see simtools.shapes.AbstractShape#getShapeName()
*/
public String getShapeName(){
return Builtin.resources.getString("Connection");
}
public Object getPropertyValue(String name) {
Object res = super.getPropertyValue(name);
if(name.equalsIgnoreCase("STROKE_COLOR")) {
res = drawColor;
} else if(name.equalsIgnoreCase("DISPLAY_FIRST_ARROW")) {
res = new Boolean(firstArrowTransform!=null);
} else if(name.equalsIgnoreCase("DISPLAY_LAST_ARROW")) {
res = new Boolean(lastArrowTransform!=null);
} else if(name.equalsIgnoreCase("CONNECTION_PATH")) {
res = path;
}
return res;
}
public void setPropertyValue(String name, Object value) {
if(name.equalsIgnoreCase("DISPLAY_FIRST_ARROW")) {
if(value instanceof Boolean) {
if(((Boolean)value).booleanValue()){
if(firstArrowTransform==null){
firstArrowTransform=new AffineTransform();
setBounds();
}
}
else{
firstArrowTransform = null;
}
}
} else if(name.equalsIgnoreCase("DISPLAY_LAST_ARROW")) {
if(value instanceof Boolean) {
if(((Boolean)value).booleanValue()){
if(lastArrowTransform==null){
lastArrowTransform=new AffineTransform();
setBounds();
}
}
else{
lastArrowTransform=null;
}
}
} else if(name.equalsIgnoreCase("CONNECTION_PATH")) {
if(value instanceof Path) {
setPath((Path) path);
}
}
super.setPropertyValue(name, value);
}
/* (non-Javadoc)
* @see jsynoptic.builtin.Abstract1DShape#translate(int, int)
*/
public void translate(int dx, int dy) {
path.translatePath(dx, dy);
setBounds();
}
protected void setBounds() {
// TODO set bounds with the path...
/* bounds2D= path.getBounds();
_ox= (int)bounds2D.getMinX();
_oy= (int)bounds.getMinY();
_w = (int)bounds.getWidth();
_h = (int)bounds.getHeight();
if(_w==0){
_w=1;
}
if(_h==0){
_h=1;
}*/
Point p = path.getNode(0);
_ox=(int)p.x;
_oy=(int)p.y;
int n=path.getNodeNumber();
for(int i=1;i<n;i++) {
p = path.getNode(i);
if (_ox>(int)p.x) {
_ox=(int)p.x;
}
if (_oy>p.y) {
_oy=(int)p.y;
}
}
_w=0;
_h=0;
for(int i=0;i<n;i++) {
p = path.getNode(i);
if ((_w+_ox)<p.x) {
_w=(int)p.x-_ox;
}
if ((_h+_oy)<p.y) {
_h=(int)p.y-_oy;
}
}
if(_w==0){
_w=1;
}
if(_h==0){
_h=1;
}
_y=_h;
if (firstArrowTransform!=null && path.getNodeNumber()>1){
Point p0 = path.getNode(0);
Point p1 = path.getNode(1);
double theta = Math.atan2((p1.y - p0.y),(p1.x - p0.x));
firstArrowTransform.setToTranslation(-p0.x, -p0.y);
firstArrowTransform.rotate(theta);
}
if (lastArrowTransform!=null && path.getNodeNumber()>1){
Point pn1 = path.getNode(0);
Point pn2 = path.getNode(1);
double theta = Math.atan2((pn2.y - pn1.y),(pn2.x - pn1.x));
lastArrowTransform.setToTranslation(-pn1.x, -pn1.y);
lastArrowTransform.rotate(theta);
}
updateBounds();
}
/**
* return the first segment number wich intersects with
* the provided rectangle or -1 if none
*/
public int intersects(int ox,int oy, int fx, int fy) {
if ((fx<_ox)|| (fy<_oy)|| (ox>(_ox+_w))|| (oy>(_oy+_h))) {
return -1;
}
return 0;
}
//
// Shape Interface
//
public boolean contains(double ox, double oy) {
return path.contains(ox, oy);
}
public boolean intersects(double x, double y, double w, double h) {
int ox=(int)x;
int oy=(int)y;
int fx=(int)(x+w);
int fy=(int)(y+h);
return intersects(ox,oy,fx,fy)>0;
}
public void draw(Graphics2D g) {
Color oldColor = g.getColor();
Color oldBackground = g.getBackground();
Stroke oldStroke = g.getStroke();
Object antialiasHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
AbstractShape.ANTI_ALIASING
? RenderingHints.VALUE_ANTIALIAS_ON
: RenderingHints.VALUE_ANTIALIAS_OFF);
AffineTransform oldTransform=null;
if(transform!=null){
oldTransform=g.getTransform();
g.transform(transform.getTransform());
}
drawHook(g,false);
//stroke
g.setStroke(stroke);
Color c = getDrawColor();
if (c!=null) {
g.setColor(c);
// Draw lines
path.drawPath(g);
// draw arrows
if (firstArrowTransform!=null){
Shape s = firstArrowTransform.createTransformedShape(arrow);
g.fill(s);
g.draw(s);
}
if (lastArrowTransform!=null){
Shape s = lastArrowTransform.createTransformedShape(arrow);
g.fill(s);
g.draw(s);
}
}
drawHook(g,true);
if(oldTransform!=null){
g.setTransform(oldTransform);
}
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasHint);
g.setBackground(oldBackground);
g.setColor(oldColor);
g.setStroke(oldStroke);
}
/* (non-Javadoc)
* @see simtools.diagram.LineInterface#pointsNumber()
*/
public int getPointsNumber() {
// Node and Segment translator points
return path.getNodeNumber() * 2 -1;
}
/* (non-Javadoc)
* @see simtools.diagram.SelectableShapePointsInterface#getPoint(int)
*/
public Point getPoint(int index) {
Point res;
if (index % 2 == 0){
res = path.getNode(index /2);
} else{
Point p1 = path.getNode(index/2);
Point p2 = path.getNode(index/2 + 1);
res = new Point( (p1.x + p2.x )/2, (p1.y + p2.y )/2 );
}
return res;
}
/* (non-Javadoc)
* @see simtools.diagram.LineInterface#translatePoint(int, int, int)
*/
public int translatePoint(int pointIndex, int dx, int dy) {
if (pointIndex % 2 == 0){ // translate a node
pointIndex = path.translateNode(pointIndex / 2, dx, dy) * 2 ;
} else { // translate a segment
pointIndex = path.translateSegment( (pointIndex / 2) , (dx!=0)? dx : dy) * 2 + 1;
}
setBounds();
return pointIndex;
}
/* (non-Javadoc)
* @see jsynoptic.builtin.Abstract1DShape#getDelegateShape()
*/
protected Shape getDelegateShape() {
return this;
}
//
// Serialization
//
private void readObject(ObjectInputStream s) throws IOException,
ClassNotFoundException {
// read persitent fields
s.defaultReadObject();
// backward compatiblity
if(displayFirstArrow){
firstArrowTransform=new AffineTransform();
}
if(displayLastArrow){
lastArrowTransform=new AffineTransform();
}
if (path == null){
setPath(new StraightPath(_xv, _yv));
}
_xv = null;
_yv = null;
// update bounds
setBounds();
}
public String[] getPropertyNames(){
if (_propertyNames==null)
_propertyNames = new ConnectionShapePropertiesNames().getPropertyNames();
return _propertyNames;
}
public static class ConnectionShapePropertiesNames extends Abstract1DShapePropertiesNames{
private static transient String[] props = new String[] { "DISPLAY_FIRST_ARROW", "DISPLAY_LAST_ARROW", "CONNECTION_PATH" };
public ConnectionShapePropertiesNames(){
super();
for (int i=0;i<props.length;i++){
propertyNames.add(props[i]);
}
}
}
/* Connect a connector bound to a gate.
* @param gate - The gate to be attached
* @param onfirstBound - If true attach the first bound, otherwise attach the last bound.
*/
public void connect(Gate gate, boolean onfirstBound) {
// Proceed to the connection only if the target gate allows the connection with the other gate
if (gate != null && gate.allowConnection(onfirstBound? lastGate : fisrtGate)){
gate.connectTo(this);
if (onfirstBound){
fisrtGate = gate;
path.lockFirstNode(true);
}else {
lastGate = gate;
path.lockLastNode(true);
}
}
}
/**
* Disconnect the bound from its attached gate
* @param firstBound
*/
public void disconnect(Gate gate){
if (isConnected(gate)){
gate.disconnectFrom(this);
if (gate.equals(fisrtGate)){
fisrtGate = null;
path.lockFirstNode(false);
} else {
lastGate = null;
path.lockLastNode(false);
}
}
}
public Gate getLastEndGate() {
return lastGate;
}
public Gate getFirstEndGate() {
return fisrtGate;
}
/* (non-Javadoc)
* @see simtools.diagram.gate.Connection#isConnected(boolean)
*/
public boolean isConnected(Gate gate) {
return (gate!= null && ( gate==fisrtGate || gate==lastGate));
}
/* (non-Javadoc)
* @see simtools.diagram.gate.Connection#getPath()
*/
public Path getPath() {
return path;
}
public void setPath(Path path){
this.path = path;
this.path.lockFirstNode(fisrtGate != null);
this.path.lockLastNode(lastGate != null);
setBounds();
}
/* (non-Javadoc)
* @see simtools.diagram.SelectableShapePointsInterface#createSelection()
*/
public ElementSelection createSelection(Point shapeOrigin, DiagramSelection ds) {
return new ConnectionPathSelection(this,shapeOrigin);
}
/* (non-Javadoc)
* @see simtools.diagram.gate.Connection#updateConnectionEnds()
*/
public void gatePositionHasChanged(Gate gate) {
boolean isDirty = false;
if (gate == fisrtGate){
Point fn = path.getNode(0);
path.translateNode(0,fisrtGate.getAnchor().x - fn.x, fisrtGate.getAnchor().y - fn.y);
isDirty = true;
} else if(gate == lastGate){
Point ln = path.getNode(path.getNodeNumber()-1);
path.translateNode(path.getNodeNumber()-1,lastGate.getAnchor().x - ln.x, lastGate.getAnchor().y - ln.y);
isDirty = true;
}
if (isDirty){
setBounds();
}
}
public String toString(){
return path.toString();
}
}