/* ==============================================
* Simtools : The tools library used in JSynoptic
* ==============================================
*
* 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 2001-2003, by :
* Corporate:
* Astrium SAS
* EADS CRC
* Individual:
* Claude Cazenave
* Nicolas Brodu
*
*
* $Id: AbstractShape.java,v 1.27 2009/01/08 16:40:18 ogor Exp $
*
* Changes
* -------
* 25-Sep-2003 : Initial public release (NB);
*
*/
package simtools.shapes;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.swing.undo.CompoundEdit;
import simtools.diagram.Element;
import simtools.shapes.ShapeListener;
import simtools.shapes.ui.AbstractShapePropertiesDialogBox;
import simtools.shapes.undo.PropertyChangeEdit;
import simtools.ui.JPropertiesPanel;
import simtools.util.ListenerManager;
import simtools.util.NamedProperties;
/**
* This base class is used for shapes with a specific bounds computation
* based on anchor definition. The derived classes can be serialized.
*
* @author Claude Cazenave
*
* @version 1.0 2001
*/
public abstract class AbstractShape implements java.io.Serializable, Cloneable, Element,
NamedProperties {
static final long serialVersionUID = -7697012777559414541L;
public static int MIN_SIZE = 20;
protected int _x;
protected int _y;
protected int _w;
protected int _h;
/** anchor location on x */
protected int _ox;
/** anchor location on y */
protected int _oy;
/**
* The list of properties managed by this panel
*/
protected transient String[] _propertyNames=null;
protected transient ListenerManager listeners = new ListenerManager();
/**
* Refresh at no more than 100 ms
*/
static public int REFRESH_PERIOD = 100;
/**
* Use anti aliasing
*/
static public boolean ANTI_ALIASING = true;
/**
* Available font list
*/
static public String[] FONT_NAMES={"Dialog", "Lucida Bright", "Lucida Sans", "Monospaced", "SansSerif", "Serif"};
/** Only one shape properties dialog box can be displayed at once
* When user opens a dialog box properties while another one is still displayed: this last dislaog box is closed
*/
public transient static AbstractShapePropertiesDialogBox currentDialogBox=null;
/**
* Initializes this abstract shape, anchored at 0,0
*/
public AbstractShape() {
this(0,0);
}
/**
* Initializes this abstract shape
* @param ox the anchor x position
* @param oy the anchor y position
*/
public AbstractShape(int ox, int oy){
_ox=ox;
_oy=oy;
_x=0;
_y=0;
_w=0;
_h=0;
}
/**
* Performs a copy of the shape
* This method has to be overriden to deal with concrete shapes
* @return a copy of the shape
*/
protected AbstractShape cloneShape(){
try{
AbstractShape clone = (AbstractShape)super.clone();
clone.listeners = new ListenerManager();
return clone;
}
catch(CloneNotSupportedException cnse){
}
return null;
}
/**
* Compares shape left/top origin with a point
* If the shape origin is lowered then replace the
* point coordinates with the minimum corrdinates
* @param p the point
* @param pa the anchor origin
*/
public void getMin(Point p){
int x=_ox+_x;
int y=_oy+_y-_h;
if(x<p.x){
p.x=x;
}
if(y<p.y){
p.y=y;
}
}
/**
* Gets shape anchor
* @return the origin
*/
public Point getAnchor(){
return new Point(_ox,_oy);
}
/**
* Get an optional affineTransform applied to this shape on display.
* @return
*/
public AffineTransform getTransform(){
return null;
}
/**
* Sets shape anchor
* @param p the origin
*/
public void setAnchor(Point p) {
setAnchor(p.x, p.y);
}
public void setAnchor(int ox, int oy){
_ox=ox;
_oy=oy;
}
/**
* Compare shape right/down coordinates with a point
* If the shape coordiantes are lowered then replace the
* point coordinates with the maximum value of the coordinates
* @param p the point
*/
public void getMax(Point p){
int x=_ox+_x+_w;
int y=_oy+_y;
if(x>p.x){
p.x=x;
}
if(y>p.y){
p.y=y;
}
}
/**
* Compares shape right/down coordinates with a transalted point
* If the shape coordiantes are lowered then replace the
* point coordinates with the maximum value of the coordinates
* @param p the point
* @param pa the anchor origin
* @param dx translation along X axis
* @param dy translation along Y axis
*/
public void getMaxTranslated(Point p, int dx, int dy){
int x=_ox+_x+_w+dx;
int y=_oy-_y;
if(x>p.x){
p.x=x;
}
if(y>p.y){
p.y=y;
}
}
/**
* Translates the shape
*/
public void translate(int dx, int dy){
_ox+=dx;
_oy+=dy;
}
/**
* Draws the shape
* @param g the graphics context
*/
public abstract void draw(Graphics2D g);
//
// Shape Interface
//
public boolean contains(double x, double y){
int ox=_ox+_x;
int oy=_oy+_y;
return (x >= ox)
&& (x <= (ox+_w))
&& (y >= (oy-_h))
&& (y <= oy);
}
public boolean contains(double x, double y, double w, double h){
int ox=_ox+_x;
int oy=_oy+_y;
return (x>=ox)
&& ((x+w)<=(ox+_w))
&& (y>=(oy-_h))
&& ((y+h)<=oy);
}
public boolean contains(Point2D p){
return contains(p.getX(), p.getY());
}
public boolean contains(Rectangle2D r){
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
public boolean intersects(double x, double y, double w, double h){
return getBounds2D().intersects(x,y,w,h);
}
public boolean intersects(Rectangle2D r){
return getBounds2D().intersects(r);
}
public Rectangle getBounds(){
return new Rectangle(_ox+_x,_oy+_y-_h,_w,_h);
}
public Rectangle2D getBounds2D(){
return new Rectangle2D.Double(_ox+_x,_oy+_y-_h,_w,_h);
}
public PathIterator getPathIterator(AffineTransform at){
return null;
}
public PathIterator getPathIterator(AffineTransform at, double flatness){
return null;
}
// Shape Interface end
// Listeners related functions
public void addListener(ShapeListener sl) {
listeners.add(sl);
}
public void removeListener(ShapeListener sl) {
listeners.remove(sl);
}
/* (non-Javadoc)
* @see simtools.diagram.Element#processShapeRestoring()
*/
public void processShapeRestoring(){
// by default do nothing
}
/* (non-Javadoc)
* @see simtools.diagram.Element#processShapeRemoving()
*/
public void processShapeRemoving(){
// by default do nothing
}
/**
* Notify listeners that this shape has changed.
* This is called by the various subclasses when necessary. For example, when connected
* to dynamic data sources.
*/
protected synchronized void notifyChange() {
notifyChange(null);
}
protected synchronized void notifyChange(Rectangle changedArea) {
if (listeners.size()==0) return;
Rectangle r=getBounds();
if (changedArea!=null){
r.add(changedArea);
}
synchronized(listeners) {
int n = listeners.size(); // only one call outside loop
for (int i=0; i<n; ++i) {
ShapeListener sl = (ShapeListener)listeners.get(i);
if (sl!=null) sl.shapeChanged(AbstractShape.this, r);
}
}
}
/**
* Restores a serialized object.
*
* @param stream the input stream.
*
* @throws IOException if there is an I/O problem.
* @throws ClassNotFoundException if there is a problem loading a class.
*/
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
listeners = new ListenerManager();
}
/**
* Assigns <code>value</code> to the property named <code>name</code>
* @param name
* @param value
*/
public void setPropertyValue(String name, Object value) {
if(false){ // DEBUG
Object oldValue=getPropertyValue(name);
if(((oldValue==null)&&(value!=null))
||((oldValue!=null)&&!oldValue.equals(value))){
System.err.println("Property: "+name
+", "+getClass()
+", OLD: "+oldValue
+", NEW: "+value);
}
}
}
/**
* Get the panel that best fit with the given list of properties
* @param properties
* @return The panel, or null if no panel complies with the given list of properties
*/
public JPropertiesPanel getPanel(List properties){
return null;
}
/**
* @return the name of the shape
*/
public String getShapeName(){
return "";
}
/**
* Get the value of the property named <code>name</code>
* @param name
*/
public Object getPropertyValue(String name) {
return null;
}
/* (non-Javadoc)
* @see simtools.util.NamedProperties#getInnerProperties()
*/
public Collection getInnerProperties() {
return null;
}
public String[] getPropertyNames(){
return null;
}
public boolean setProperties(NamedProperties properties, CompoundEdit ce) {
boolean res=false;
String[] props = properties.getPropertyNames();
if(props!=null){
// Set local properties
for(int j=0; j<props.length; j++) {
String pname=props[j];
Object value=properties.getPropertyValue(pname);
ce.addEdit(new PropertyChangeEdit(this, pname, getPropertyValue(pname),value));
setPropertyValue(pname, value);
res=true;
}
}
// Manage inner shapes if any
Collection c = properties.getInnerProperties();
Collection cshape=getInnerProperties();
if(c!=null&&cshape!=null){
Iterator it=c.iterator();
Iterator itshape=cshape.iterator();
while(it.hasNext()&& itshape.hasNext()){
AbstractShape as=(AbstractShape)itshape.next();
NamedProperties np=(NamedProperties)it.next();
res |= as.setProperties(np,ce);
}
}
return res;
}
/**
* Wipe-off all all buffered resources in order to refresh shape.
* This is overloaded by the various subclasses when necessary.
*
* For example, a image shape shall wipe-off all its buffered images
*/
public void wipeOff(){}
/**
* Reload all shape properties.
*/
public void refresh(){
String[] names = getPropertyNames();
if (names != null){
for(int j =0; j<names.length;j++){
setPropertyValue(names [j], getPropertyValue(names [j]));
}
}
}
/**
* @param shapes - a list of shapes
* @return a list of properties that can be applied to all given shapes. Return null if list of shapes is empty
*/
protected static List getCommonProperties(List shapes){
List ret = new ArrayList();
if (!shapes.isEmpty()){
String[] properties = ((AbstractShape)shapes.get(0)).getPropertyNames();
for(int i=0; i < properties.length; i++){
String property = properties[i];
boolean isCommonProperty = true;
for(int j=1; (j< shapes.size()) && isCommonProperty; j++){
AbstractShape s = (AbstractShape)shapes.get(j);
isCommonProperty &= Arrays.asList(s.getPropertyNames()).contains(property);
}
if (isCommonProperty){
ret.add(property);
}
}
}
return ret;
}
/**
* A delegate class that provides the list of properties names related to its mother class
* @author zxpletran007
*
*/
public static class AbstractShapePropertiesNames{
protected ArrayList propertyNames;
public AbstractShapePropertiesNames(){
propertyNames = new ArrayList();
}
public String[] getPropertyNames(){
return (String[])propertyNames.toArray(new String[propertyNames.size()]);
}
}
}