/*
* Copyright (C) 2004 The Concord Consortium, Inc.,
* 10 Concord Crossing, Concord, MA 01742
*
* Web Site: http://www.concord.org
* Email: info@concord.org
*
* 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
*
* END LICENSE */
package org.concord.swing;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Vector;
import javax.swing.JPopupMenu;
import javax.swing.JTextPane;
public class JAnnotationImageModel implements StateOwner{
public final static int CHOOSING_MODE_RECTANGLE = 0;
public final static int CHOOSING_MODE_ELLIPSE = 1;
public final static int CHOOSING_MODE_POLYGON = 2;
public final static int CHOOSING_MODE_POINTS = 3;
public final static int CHOOSING_MODE_MIN = CHOOSING_MODE_RECTANGLE;
public final static int CHOOSING_MODE_MAX = CHOOSING_MODE_POINTS;
transient private java.awt.Image image;
transient BufferedImage bim;
transient BufferedImage bimW;
transient BufferedImage bimF;
transient String imageURLString;
transient AnnotationSpot selectedAnnotationSpot;
int choosingMode = CHOOSING_MODE_RECTANGLE;
boolean editMode = false;
String imageResourceString;
LinkedList annotationSpots = null;
boolean toolTipMode = false;
private ModelState state;
public JAnnotationImageModel(){
}
public JAnnotationImageModel(Object state){
this();
setState(state);
}
public JAnnotationImageModel(String resString){
this();
setImageResourceString(resString);
}
public JAnnotationImageModel(BufferedImage bim){
this();
createImage(bim);
}
public void setImageResourceString(String imageResourceString){
this.imageResourceString = imageResourceString;
setImageURLString();
}
public void setImageResourceString1(String imageResourceString){
this.imageResourceString = imageResourceString;
}
public String getImageResourceString(){return imageResourceString;}
public void setEditMode(boolean editMode){
this.editMode = editMode;
}
public boolean isEditMode(){return editMode;}
public int getChoosingMode(){return choosingMode;}
public synchronized void setChoosingMode(int choosingMode){
this.choosingMode = choosingMode;
checkChoosingMode();
}
public LinkedList getAnnotationSpots(){return annotationSpots;}
public void setAnnotationSpots(LinkedList annotationSpots){
this.annotationSpots = annotationSpots;
}
public void clearAnnotationSpots(){
if(annotationSpots != null){
ListIterator it = annotationSpots.listIterator();
while(it.hasNext()){
AnnotationSpot as = (AnnotationSpot)it.next();
as.disposeAnnotationToolTip();
}
annotationSpots.clear();
}
updateState();
}
public void deleteSelectedSpot(){
if(annotationSpots == null || selectedAnnotationSpot == null) return;
selectedAnnotationSpot.disposeAnnotationToolTip();
annotationSpots.remove(selectedAnnotationSpot);
updateState();
}
public String getToolTipText(java.awt.event.MouseEvent event){
if(annotationSpots == null || !isToolTipMode()) return null;
Point pt = event.getPoint();
ListIterator it = annotationSpots.listIterator(annotationSpots.size());
while(it.hasPrevious()){
AnnotationSpot as = (AnnotationSpot)it.previous();
if(as.contains(pt)) return as.getAnnotation();
}
return null;
}
public void setSelectedAnnotationSpot(AnnotationSpot as){
selectedAnnotationSpot = as;
}
public AnnotationSpot getSelectedAnnotationSpot(){return selectedAnnotationSpot;}
public AnnotationSpot getAnnotationSpotForPoint(Point pt){
AnnotationSpot foundSpot = null;
if(annotationSpots == null) return foundSpot;
ListIterator it = annotationSpots.listIterator(annotationSpots.size());
while(it.hasPrevious()){
AnnotationSpot as = (AnnotationSpot)it.previous();
int asMode = as.startDrag(null,pt);
if(asMode != AnnotationSpot.SPOT_REGION_UNKNOWN){
foundSpot = as;
break;
}
}
return foundSpot;
}
public int getCursorSpotRegionForPoint(Point pt){
int cursorSpotRegion = AnnotationSpot.SPOT_REGION_UNKNOWN;
if(annotationSpots != null){
ListIterator it = annotationSpots.listIterator(annotationSpots.size());
while(it.hasPrevious()){
AnnotationSpot as = (AnnotationSpot)it.previous();
if(isEditMode() && (selectedAnnotationSpot == as)){
Rectangle r = as.getBoundsForAdjustCursor();
if(r.contains(pt)){
cursorSpotRegion = as.getSpotRegion(pt.x,pt.y);
break;
}
}else if(!isEditMode()){
if(as.contains(pt)){
cursorSpotRegion = AnnotationSpot.SPOT_REGION_ALL;
break;
}
}
}
}
return cursorSpotRegion;
}
public synchronized void addAnnotationSpot(AnnotationSpot as){
addAnnotationSpot(as,false);
}
public synchronized void addAnnotationSpot(AnnotationSpot as,boolean forceAdd){
if(as == null || (as.isEmpty() && !forceAdd)) return;
if(annotationSpots == null) annotationSpots = new LinkedList();
annotationSpots.add(as);
as.setIndex(annotationSpots.size() - 1);
as.setToolTipMode(isToolTipMode());
}
public synchronized void removeAnnotationSpot(AnnotationSpot as){
if(annotationSpots != null && as != null && annotationSpots.contains(as)){
as.disposeAnnotationToolTip();
annotationSpots.remove(as);
}
}
public boolean isAnnotationSpotPopupVisible(){
boolean retValue = false;
if(annotationSpots == null) return retValue;
ListIterator it = annotationSpots.listIterator();
while(it.hasNext()){
AnnotationSpot as = (AnnotationSpot)it.next();
if(as.isPopupVisible()){
retValue = true;
break;
}
}
return retValue;
}
public void setToolTipMode(boolean toolTipMode){
this.toolTipMode = toolTipMode;
if(annotationSpots != null){
ListIterator it = annotationSpots.listIterator();
while(it.hasNext()){
AnnotationSpot as = (AnnotationSpot)it.next();
as.setToolTipMode(isToolTipMode());
}
}
}
public java.awt.geom.Area getToolTipAreaForClip(java.awt.Component destination){
if(isToolTipMode() || annotationSpots == null || annotationSpots.size() < 1) return null;
java.awt.geom.Area area = new java.awt.geom.Area();
ListIterator it = annotationSpots.listIterator();
while(it.hasNext()){
AnnotationSpot as = (AnnotationSpot)it.next();
java.awt.Component c = as.getAnnotationToolTip();
if(c == null) continue;
Rectangle r = c.getBounds();
java.awt.Container parent = c.getParent();
if(parent == null) continue;
area.add(new java.awt.geom.Area(javax.swing.SwingUtilities.convertRectangle(parent,r,destination)));
}
return area;
}
public boolean isToolTipMode(){
return toolTipMode;
}
public void dispose(){
if(image != null) image.flush();
if(bim != null) bim.flush();
if(bimW != null) bimW.flush();
if(bimF != null) bimF.flush();
image = null;
bim = null;
bimW = null;
bimF = null;
clearAnnotationSpots();
}
public Object getState(){
updateState();
return state;
}
protected void updateState(){
if(state == null) state = new ModelState();
state.choosingMode = choosingMode;
state.editMode = editMode;
state.imageResourceString = imageResourceString;
state.image = bim;
state.updateAnnotationSpots(annotationSpots);
}
public void setState(Object s){
if(!(s instanceof ModelState)) return;
state = (ModelState)s;
recreateFromState();
}
protected void recreateFromState(){
if(state == null) return;
choosingMode = state.getChoosingMode();
editMode = state.isEditMode();
imageResourceString = state.getImageResourceString();
setImageURLString();
annotationSpots = null;
Vector v = state.getAnnotationSpots();
if(v == null || v.isEmpty()) return;
if(annotationSpots == null) annotationSpots = new LinkedList();
else annotationSpots.clear();
for(int i = 0; i < v.size(); i++){
annotationSpots.add(new AnnotationSpot(v.elementAt(i)));
}
}
protected void setImageURLString(){
imageURLString = null;
if(imageResourceString == null) return;
String jarString = createJARURLString(imageResourceString);
if(checkForResource(jarString)){
imageURLString = jarString;
}else if(checkForResource(imageResourceString)){
imageURLString = imageResourceString;
}else{
imageURLString = "file:"+imageResourceString;
}
createImage();
}
protected void checkChoosingMode(){
if(choosingMode >= CHOOSING_MODE_MIN && choosingMode <= CHOOSING_MODE_MAX) return;
choosingMode = CHOOSING_MODE_RECTANGLE;
}
protected boolean checkForResource(String str){
boolean retValue = false;
if(str == null) return retValue;
try{
java.net.URL url = new java.net.URL(str);
java.io.InputStream is = url.openStream();
is.close();
retValue = true;
}catch(Throwable t){
retValue = false;
}
return retValue;
}
protected String createJARURLString(String str){
if(str == null) return str;
StringBuffer sb = new StringBuffer();
String classItemName = getClass().getName().replace('.','/');
if(!classItemName.startsWith("/")) sb.append("/");
sb.append(classItemName);
sb.append(".class");
java.net.URL urlClass = getClass().getResource(sb.toString());
String externalForm = urlClass.toExternalForm();
int index = externalForm.lastIndexOf('!');
if(index < 0){
return str;
}else{
String partialPath = externalForm.substring(0,index+1);
try{
sb.setLength(0);
sb.append(partialPath);
if(!str.startsWith("/")) sb.append("/");
sb.append(str);
}catch(Throwable t){
}
}
return sb.toString();
}
protected void createImage(){
createImage(null);
}
protected void createImage(BufferedImage bufferedImage){
if(image != null) image.flush();
if(bim != null) bim.flush();
if(bimW != null) bimW.flush();
if(bimF != null) bimF.flush();
image = null;
bim = null;
bimW = null;
bimF = null;
if(bufferedImage == null && imageURLString == null) return;
int width = 0;
int height = 0;
if(bufferedImage != null){
//bim = bufferedImage;
image = bufferedImage;
width = bufferedImage.getWidth();
height = bufferedImage.getHeight();
}else{
if(!checkForResource(imageURLString)){
System.out.println("resource is unavailable");
return;
}
try{
java.net.URL imageURL = new java.net.URL(imageURLString);
image = javax.imageio.ImageIO.read(imageURL);
width = image.getWidth(null);
height = image.getHeight(null);
}catch(Throwable t){
System.out.println("url throwable "+t);
}
/*
try{
java.net.URL imageURL = new java.net.URL(imageURLString);
image = java.awt.Toolkit.getDefaultToolkit().createImage(imageURL);
}catch(Throwable t){
System.out.println("url throwable "+t);
}
java.awt.MediaTracker tracker = new java.awt.MediaTracker(new java.awt.Component(){});
tracker.addImage(image,0);
try{
tracker.waitForAll();
} catch (Exception e) {}
width = image.getWidth(null);
height = image.getHeight(null);
*/
}
java.awt.GraphicsEnvironment ge = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
java.awt.GraphicsDevice gd = ge.getDefaultScreenDevice();
java.awt.GraphicsConfiguration gc = gd.getDefaultConfiguration();
boolean hasAlpha = gc.getColorModel().hasAlpha();
if(hasAlpha){
if(image != null) bim = gc.createCompatibleImage(width,height);
bimW = gc.createCompatibleImage(width,height);
bimF = gc.createCompatibleImage(width,height);
// if(image != null) bim = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB_PRE);
// bimW = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB_PRE);
// bimF = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB_PRE);
// System.out.println("type "+bim.getType());
}else{
if(image != null) bim = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
bimW = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
bimF = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
}
java.awt.Graphics2D g2d = null;
if(image != null){
g2d = bim.createGraphics();
g2d.drawImage(image,0,0,null);
g2d.dispose();
}
g2d = bimW.createGraphics();
g2d.setColor(java.awt.Color.white);
g2d.fillRect(0,0,width,height);
g2d.drawImage(bim,new TranslucentGlassOp(0,0,-1,-1),0,0);
g2d.dispose();
}
protected void removeTransientFields(){
try{
Class myClass = getClass();
java.lang.reflect.Field []fields = myClass.getDeclaredFields();
Vector transientFields = new Vector();
for(int i = 0; i < fields.length; i++){
java.lang.reflect.Field f = fields[i];
String modifiers = java.lang.reflect.Modifier.toString(f.getModifiers());
if(modifiers != null && modifiers.toLowerCase().indexOf("transient") >= 0){
transientFields.add(f.getName());
}
}
java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(getClass());
java.beans.PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors();
for (int i = 0; i < propertyDescriptors.length; ++i) {
java.beans.PropertyDescriptor pd = propertyDescriptors[i];
if(transientFields.contains(pd.getName())){
pd.setValue("transient", Boolean.TRUE);
}
}
}catch(Throwable t){}
}
protected void prepareForSerialization(){
removeTransientFields();
if(annotationSpots != null){
ListIterator it = annotationSpots.listIterator();
while(it.hasNext()){
AnnotationSpot as = (AnnotationSpot)it.next();
as.prepareForSerialization();
}
}
}
protected BufferedImage getMainImage(){return bim;}
protected BufferedImage getMainEditImage(){return bimW;}
protected BufferedImage getBackgroundImageImage(){return bimF;}
protected void checkAnnotationToolTips(){
if(annotationSpots != null){
ListIterator it = annotationSpots.listIterator();
while(it.hasNext()){
AnnotationSpot as = (AnnotationSpot)it.next();
as.checkAnnotationToolTip();
}
}
}
public static class ModelState implements java.io.Serializable{
static final long serialVersionUID = -1118530011344190681L;
int choosingMode;
boolean editMode;
String imageResourceString;
Vector annotationSpots;
transient BufferedImage image;
protected void updateAnnotationSpots(LinkedList list){
if(annotationSpots == null) annotationSpots = new Vector();
annotationSpots.removeAllElements();
if(list != null){
ListIterator it = list.listIterator();
while(it.hasNext()){
JAnnotationImageModel.AnnotationSpot as = (JAnnotationImageModel.AnnotationSpot)it.next();
annotationSpots.add(as.getState());
}
}
}
public int getChoosingMode(){return choosingMode;}
public void setChoosingMode(int choosingMode){this.choosingMode = choosingMode;}
public boolean isEditMode(){return editMode;}
public void setEditMode(boolean editMode){this.editMode = editMode;}
public String getImageResourceString(){return imageResourceString;}
public void setImageResourceString(String imageResourceString){this.imageResourceString = imageResourceString;}
public Vector getAnnotationSpots(){return annotationSpots;}
public void setAnnotationSpots(Vector annotationSpots){this.annotationSpots = (Vector)annotationSpots.clone();}
public BufferedImage getImage(){return image;}
public void setImage(BufferedImage image){this.image = image;}
}
public static class AnnotationSpot implements Shape, Draggable, StateOwner{
public final static int SPOT_REGION_UNKNOWN = 0;
public final static int SPOT_REGION_TOP_LEFT = 1;
public final static int SPOT_REGION_TOP_RIGHT = 2;
public final static int SPOT_REGION_BOTTOM_LEFT = 3;
public final static int SPOT_REGION_BOTTOM_RIGHT = 4;
public final static int SPOT_REGION_LEFT = 5;
public final static int SPOT_REGION_RIGHT = 6;
public final static int SPOT_REGION_TOP = 7;
public final static int SPOT_REGION_BOTTOM = 8;
public final static int SPOT_REGION_ALL = 9;
public final static int SPOT_REGION_DEFINE = 10;
public final static int SPOT_REGION_TOOL_TIP = 11;
static final int VICINITY_CONSTANT = 5;
String annotation;
Point2D []points;
Rectangle enclosedRectangle;
boolean toolTipMode = true;
Point annotationToolTipLocation;
Rectangle bounds;
java.awt.Color spotColor = java.awt.Color.blue;
int choosingMode = JAnnotationImageModel.CHOOSING_MODE_RECTANGLE;
transient double startDragX;
transient double startDragY;
transient GeneralPath startDragArea;
transient int dragMode;
transient GeneralPath spot;
transient StaticAnnotationToolTip annotationToolTip;
transient int index = 0;
transient boolean draggable = true;
transient Point annotationTipConnectionPoint;
transient private boolean choosingModeWasCalled = false;
Vector pickedPoints = null;
int pickedPointIndex = -1;
AnnotationSpotState state;
JPopupMenu textAreaMenu;
private javax.swing.JTextArea textArea = new javax.swing.JTextArea();
JAnnotationImage annotationImage;
CoordinateTransformer currentTransformer;
transient private boolean popupMenuVisible = false;
protected AnnotationSpot(Object state){
this();
setState(state);
}
public AnnotationSpot(){
}
public AnnotationSpot(Shape spot){
this("",spot);
}
public AnnotationSpot(String annotation,Shape spot){
this();
this.annotation = annotation;
this.spot = new GeneralPath(spot);
}
public JAnnotationImage getAnnotationImage(){return annotationImage;}
public void setAnnotationImage(JAnnotationImage annotationImage){this.annotationImage = annotationImage;}
public boolean getHtmlSupport(){
return (annotationImage == null)?false:annotationImage.getHtmlSupport();
}
public synchronized boolean isPopupVisible(){
return popupMenuVisible;
}
public synchronized void setPopupVisible(boolean val){
if(val == popupMenuVisible) return;
if(!popupMenuVisible){
popupMenuVisible = val;
return;
};
javax.swing.Timer swingTimer = new javax.swing.Timer(500,new java.awt.event.ActionListener(){
public void actionPerformed(java.awt.event.ActionEvent evt){
synchronized(AnnotationSpot.this){
popupMenuVisible = false;
}
}
});
swingTimer.setRepeats(false);
swingTimer.start();
}
public Object getState(){
updateState();
return state;
}
protected void updateState(){
prepareForSerialization();
if(state == null) state = new AnnotationSpotState();
state.index = index;
state.choosingMode = choosingMode;
state.annotation = annotation;
state.annotationToolTipLocation = annotationToolTipLocation;
state.bounds = bounds;
state.spotColor = spotColor;
state.toolTipMode = toolTipMode;
state.points = null;
if(points != null){
state.points = (Point2D[])points.clone();
}
}
public void setState(Object s){
if(!(s instanceof AnnotationSpotState)) return;
state = (AnnotationSpotState)s;
recreateFromState();
}
protected void recreateFromState(){
if(state == null) return;
index = state.getIndex();
choosingMode = state.getChoosingMode();
annotation = state.getAnnotation();
spotColor = state.getSpotColor();
toolTipMode = state.isToolTipMode();
setPoints((Point2D[])state.getPoints());
setAnnotationToolTipLocation(state.getAnnotationToolTipLocation());
checkAnnotationToolTip();
setBounds(state.getBounds());
calculateAnnotationTipConnectionPoint();
}
public String toString(){
prepareForSerialization();
StringBuffer sb = new StringBuffer();
sb.append("<class>"+getClass().getName()+"</class>\n");
sb.append("<annotation>");
if(annotation != null) sb.append(annotation);
sb.append("</annotation>/n");
sb.append("<enclosedRectangle>");
if(enclosedRectangle != null){
sb.append("<x>"+enclosedRectangle.x+"/x\n");
sb.append("<y>"+enclosedRectangle.x+"/y"+"\n");
sb.append("<width>"+enclosedRectangle.x+"/width\n");
sb.append("<height>"+enclosedRectangle.x+"/height\n");
}
sb.append("</enclosedRectangle>\n");
sb.append("<points>");
if(points == null){
sb.append("<size>-1</size>\n");
}else{
sb.append("<size>"+points.length+"</size>\n");
for(int i = 0; i < points.length; i++){
sb.append("<point> x=\""+points[i].getX()+"\" y=\""+points[i].getY()+"\"</point>\n");
}
}
sb.append("</points>\n");
return sb.toString();
}
public int getIndex(){return index;}
public void setIndex(int index){this.index = index;}
public void setEnclosedRectangle(Rectangle r){
enclosedRectangle = r;
}
public Rectangle getEnclosedRectangle(){return enclosedRectangle;}
public void setAnnotationToolTip(StaticAnnotationToolTip annotationToolTip){
this.annotationToolTip = annotationToolTip;
}
public javax.swing.JComponent getAnnotationToolTip(){
if(isToolTipMode()) return null;
return annotationToolTip;
}
public void checkAnnotationToolTipPosition(){
if(annotationToolTip == null) createAnnotationToolTip();
if(annotationToolTip == null) return;
Rectangle rb = getBounds();
Rectangle r = annotationToolTip.getBounds();
Point pta = annotationToolTip.getLocation();
annotationToolTip.setLocation(rb.x + rb.width / 2 - r.width / 2,
rb.y + rb.height / 2 - r.height / 2);
}
public void checkAnnotationToolTip(){
if(annotationToolTip == null) createAnnotationToolTip();
}
protected void calculateAnnotationTipConnectionPoint(){
if(isEmpty()){
annotationTipConnectionPoint = new Point(Integer.MIN_VALUE,Integer.MIN_VALUE);
return;
}
Rectangle rb = getBounds();
int xc = rb.x + rb.width / 2;
int yc = rb.y + rb.height / 2;
annotationTipConnectionPoint = new Point(xc,yc);
if(contains(annotationTipConnectionPoint)) return;
java.awt.geom.PathIterator pit = spot.getPathIterator(null);
double []tempArray = new double[6];
double d2 = java.lang.Double.MAX_VALUE;
while(!pit.isDone()){
int type = pit.currentSegment(tempArray);
if(type == java.awt.geom.PathIterator.SEG_MOVETO || type == java.awt.geom.PathIterator.SEG_LINETO){
double temp = Point2D.distanceSq(xc,yc,tempArray[0],tempArray[1]);
if(temp < d2){
d2 = temp;
annotationTipConnectionPoint = new Point((int)Math.round(tempArray[0]),(int)Math.round(tempArray[1]));
}
}
pit.next();
}
}
public Point getAnnotationTipConnectionPoint(){
if(annotationTipConnectionPoint == null) calculateAnnotationTipConnectionPoint();
if(!isEmpty() && annotationTipConnectionPoint != null){
if(annotationTipConnectionPoint.x == Integer.MIN_VALUE ||
annotationTipConnectionPoint.y == Integer.MIN_VALUE) calculateAnnotationTipConnectionPoint();
}
return annotationTipConnectionPoint;
}
public void createAnnotationToolTip(){
if(annotationToolTip == null){
annotationToolTip = new StaticAnnotationToolTip(getHtmlSupport());
}
annotationToolTip.setText(annotation);
annotationToolTip.setOwner(this);
if(annotationToolTipLocation == null) annotationToolTipLocation = new java.awt.Point();
setAnnotationToolTipLocation(annotationToolTipLocation);
}
public void disposeAnnotationToolTip(){
if(annotationToolTip == null) return;
java.awt.Container parent = annotationToolTip.getParent();
if(parent != null){
parent.remove(annotationToolTip);
}
annotationToolTip = null;
}
public Point getAnnotationToolTipLocation(){return annotationToolTipLocation;}
public void setAnnotationToolTipLocation(java.awt.Point annotationToolTipLocation){
this.annotationToolTipLocation = annotationToolTipLocation;
if(annotationToolTip != null) annotationToolTip.setLocation(annotationToolTipLocation);
}
public GeneralPath getSpot(){return spot;}
public int getChoosingMode(){return choosingMode;}
public synchronized void setChoosingMode(int choosingMode){
this.choosingMode = choosingMode;
if(!choosingModeWasCalled){
choosingModeWasCalled = true;
setBounds(bounds);
}
}
public Rectangle getBoundsForAdjustCursor(){
Rectangle r = getBounds();
if(r == null) return r;
r.x -= VICINITY_CONSTANT;
r.y -= VICINITY_CONSTANT;
r.width += 2*VICINITY_CONSTANT;
r.height += 2*VICINITY_CONSTANT;
return r;
}
public java.awt.Color getSpotColor(){return spotColor;}
public void getSpotColor(java.awt.Color spotColor){this.spotColor = spotColor;}
public String getAnnotation(){return annotation;}
public void setAnnotation(String annotation){
this.annotation = annotation;
if(this.annotation != null && annotationToolTip != null){
annotationToolTip.setText(annotation);
}
}
public int getSpotRegion(Point pt){
return getSpotRegion(pt.getX(),pt.getY());
}
public int getSpotRegion(double x,double y){
Rectangle r = getBounds();
if(r == null) return SPOT_REGION_UNKNOWN;
if(isEmpty()) return SPOT_REGION_DEFINE;
if(annotationToolTip != null && !isToolTipMode()){
Rectangle annotationToolTipBounds = annotationToolTip.getBounds();
if(annotationToolTipBounds.contains(x,y)) return SPOT_REGION_TOOL_TIP;
}
if(r.width >= 0 && r.height >= 1){
int rightX = r.x + r.width;
int bottomY = r.y + r.height;
if(r.x > x && r.x - x < VICINITY_CONSTANT && r.y > y && r.y - y < VICINITY_CONSTANT) return SPOT_REGION_TOP_LEFT;
if(x > rightX && x - rightX < VICINITY_CONSTANT && r.y > y && r.y - y < VICINITY_CONSTANT) return SPOT_REGION_TOP_RIGHT;
if(r.x > x && r.x - x < VICINITY_CONSTANT && y > bottomY && y - bottomY < VICINITY_CONSTANT) return SPOT_REGION_BOTTOM_LEFT;
if(x > rightX && x - rightX < VICINITY_CONSTANT && y > bottomY && y - bottomY < VICINITY_CONSTANT) return SPOT_REGION_BOTTOM_RIGHT;
if(r.x > x && r.x - x < VICINITY_CONSTANT && y > r.y && y < bottomY) return SPOT_REGION_LEFT;
if(x > rightX && x - rightX < VICINITY_CONSTANT && y > r.y && y < bottomY) return SPOT_REGION_RIGHT;
if(r.y > y && r.y - y < VICINITY_CONSTANT && x > r.x && x < rightX) return SPOT_REGION_TOP;
if(y > bottomY && y - bottomY < VICINITY_CONSTANT && x > r.x && x < rightX) return SPOT_REGION_BOTTOM;
/*
if(Math.abs(x-r.x) < VICINITY_CONSTANT && Math.abs(y-r.y) < VICINITY_CONSTANT) return SPOT_REGION_TOP_LEFT;
if(Math.abs(x-rightX) < VICINITY_CONSTANT && Math.abs(y-r.y) < VICINITY_CONSTANT) return SPOT_REGION_TOP_RIGHT;
if(Math.abs(x-r.x) < VICINITY_CONSTANT && Math.abs(y-bottomY) < VICINITY_CONSTANT) return SPOT_REGION_BOTTOM_LEFT;
if(Math.abs(x-rightX) < VICINITY_CONSTANT && Math.abs(y-bottomY) < VICINITY_CONSTANT) return SPOT_REGION_BOTTOM_RIGHT;
if(Math.abs(x-r.x) < VICINITY_CONSTANT && y > r.y && y < bottomY) return SPOT_REGION_LEFT;
if(Math.abs(x-rightX) < VICINITY_CONSTANT && y > r.y && y < bottomY) return SPOT_REGION_RIGHT;
if(Math.abs(y-r.y) < VICINITY_CONSTANT && x > r.x && x < rightX) return SPOT_REGION_TOP;
if(Math.abs(y-bottomY) < VICINITY_CONSTANT && x > r.x && x < rightX) return SPOT_REGION_BOTTOM;
*/
if(spot.contains(x,y)) return SPOT_REGION_ALL;
}
return SPOT_REGION_UNKNOWN;
}
public int startDrag(CoordinateTransformer transformer,Point pt){
if(pickedPoints == null) pickedPoints = new Vector();
else pickedPoints.removeAllElements();
pickedPointIndex = -1;
startDragX = pt.getX();
startDragY = pt.getY();
dragMode = getSpotRegion(pt);
startDragArea = (spot != null)?(GeneralPath)spot.clone():null;
currentTransformer = transformer;
if(dragMode < SPOT_REGION_TOP_LEFT || dragMode > SPOT_REGION_BOTTOM){
pickedPointIndex = checkClickOnLine(pt,pickedPoints);
recreateSpotWithPickedLine(pt);
if(pickedPoints.size() > 0 && pickedPointIndex >= 0 && dragMode == SPOT_REGION_UNKNOWN) dragMode = SPOT_REGION_ALL;
}
return dragMode;
}
public boolean contains(Point pt){
if(pt == null || spot == null) return false;
return spot.contains(pt);
}
public boolean isEmpty(){
if(spot == null) return true;
return spot.getBounds().isEmpty();
}
public void doDrag(Point pt){
if(dragMode == SPOT_REGION_UNKNOWN) return;
double newX = pt.getX();
double newY = pt.getY();
if(enclosedRectangle != null){
if(newX < enclosedRectangle.x) newX = enclosedRectangle.x;
if(newX > enclosedRectangle.x + enclosedRectangle.width) newX = enclosedRectangle.x + enclosedRectangle.width;
if(newY < enclosedRectangle.y) newY = enclosedRectangle.y;
if(newY > enclosedRectangle.y + enclosedRectangle.height) newY = enclosedRectangle.y + enclosedRectangle.height;
}
double dx = newX - startDragX;
double dy = newY - startDragY;
if(dragMode == SPOT_REGION_DEFINE && spot != null){
double x0 = Math.min(newX,startDragX);
double y0 = Math.min(newY,startDragY);
double w0 = Math.abs(dx);
double h0 = Math.abs(dy);
switch(choosingMode){
case JAnnotationImageModel.CHOOSING_MODE_POINTS:
break;
case JAnnotationImageModel.CHOOSING_MODE_POLYGON:
spot.lineTo((float)newX,(float)newY);
break;
case JAnnotationImageModel.CHOOSING_MODE_ELLIPSE:
spot.reset();
spot.append(new GeneralPath(new java.awt.geom.Ellipse2D.Double(x0,y0,w0,h0)),true);
break;
default:
spot.reset();
spot.append(new GeneralPath(new java.awt.geom.Rectangle2D.Double(x0,y0,w0,h0)),true);
break;
}
}else if(dragMode == SPOT_REGION_ALL){
if(pickedPointIndex >= 0 && pickedPoints != null && pickedPoints.size() > 1){
recreateSpotWithPickedLine(new Point((int)Math.round(newX),(int)Math.round(newY)));
return;
}else{
GeneralPath newSpot = (GeneralPath)startDragArea.clone();
newSpot.transform(java.awt.geom.AffineTransform.getTranslateInstance(dx,dy));
if(enclosedRectangle == null){
spot = newSpot;
return;
}else if(newSpot.intersects(enclosedRectangle)){
spot = newSpot;
}
}
return;
}
Rectangle startRect = startDragArea.getBounds();
double kx = 0;
double ky = 0;
double w0 = startRect.width;
double h0 = startRect.height;
double w = w0;
double h = h0;
boolean doTransform = (spot != null);
if(doTransform){
switch(dragMode){
case SPOT_REGION_TOP_LEFT:
kx = 1;
ky = 1;
w = startRect.x + w0 - newX;
h = startRect.y + h0 - newY;
break;
case SPOT_REGION_TOP_RIGHT:
ky = 1;
w = newX - startRect.x;
h = startRect.y + h0 - newY;
break;
case SPOT_REGION_BOTTOM_LEFT:
kx = 1;
w = startRect.x + w0 - newX;
h = newY - startRect.y;
break;
case SPOT_REGION_BOTTOM_RIGHT:
w = newX - startRect.x;
h = newY - startRect.y;
break;
case SPOT_REGION_LEFT:
kx = 1;
w = startRect.x + w0 - newX;
break;
case SPOT_REGION_RIGHT:
w = newX - startRect.x;
break;
case SPOT_REGION_TOP:
ky = 1;
h = startRect.y + h0 - newY;
break;
case SPOT_REGION_BOTTOM:
h = newY - startRect.y;
break;
default:
doTransform = false;
break;
}
}
if(doTransform){
if(w < 5) w = 5;
if(h < 5) h = 5;
double m00 = w/w0;
double m01 = 0;
double m02 = (w0 - w)*(kx + startRect.x/w0);
double m10 = 0;
double m11 = h/h0;
double m12 = (h0 - h)*(ky + startRect.y/h0);
spot = (GeneralPath)startDragArea.clone();
spot.transform(new java.awt.geom.AffineTransform(m00,m10,m01,m11,m02,m12));
}
}
public void endDrag(Point pt){
startDragArea = null;
calculateAnnotationTipConnectionPoint();
if(choosingMode == JAnnotationImageModel.CHOOSING_MODE_POINTS) return;
if(choosingMode == JAnnotationImageModel.CHOOSING_MODE_POLYGON || (pickedPointIndex >= 0)) normalize();
}
public Point2D []getPoints(){return points;}
public void setPoints(Point2D []points){
this.points = points;
if(this.points == null) return;
if(spot != null) spot.reset();
else spot = new GeneralPath();
for(int i = 0; i < points.length; i++){
if(i == 0){
spot.moveTo((float)points[i].getX(),(float)points[i].getY());
}else{
spot.lineTo((float)points[i].getX(),(float)points[i].getY());
}
}
spot.closePath();
}
public void setToolTipMode(boolean toolTipMode){
this.toolTipMode = toolTipMode;
if(annotationToolTip != null){
annotationToolTip.setVisible(!toolTipMode);
}
}
public boolean isToolTipMode(){
return toolTipMode;
}
//Draggable methods
public void setDraggable(boolean draggable){
this.draggable = draggable;
}
public boolean isDraggable(){
return (dragMode != SPOT_REGION_UNKNOWN) && draggable;
}
//Shape methods
public boolean contains(double x, double y){
if(spot == null) return false;
return spot.contains(x,y);
}
public boolean contains(double x, double y,double w,double h){
if(spot == null) return false;
return spot.contains(x,y,w,h);
}
public boolean contains(Point2D p2d){
if(p2d == null || spot == null) return false;
return spot.contains(p2d);
}
public boolean contains(Rectangle2D r2d){
if(r2d == null || spot == null) return false;
return spot.contains(r2d);
}
public Rectangle getBounds(){
bounds = (spot == null)?null:spot.getBounds();
return bounds;
}
public void setBounds(Rectangle r){
if(r == null) return;
if(spot == null) spot = new GeneralPath();
switch(choosingMode){
case JAnnotationImageModel.CHOOSING_MODE_RECTANGLE:
spot.reset();
spot.append(new GeneralPath(new java.awt.geom.Rectangle2D.Double(r.x,r.y,r.width,r.height)),true);
break;
case JAnnotationImageModel.CHOOSING_MODE_ELLIPSE:
spot.reset();
spot.append(new GeneralPath(new java.awt.geom.Ellipse2D.Double(r.x,r.y,r.width,r.height)),true);
break;
case JAnnotationImageModel.CHOOSING_MODE_POLYGON:
case JAnnotationImageModel.CHOOSING_MODE_POINTS:
if(isEmpty()) break;
Rectangle rb = getBounds();
double sx = (double)r.width/(double)rb.width;
double sy = (double)r.height/(double)rb.height;
if(Math.abs(sx - 1) < 1e-5 && Math.abs(sy - 1) < 1e-5) break;
GeneralPath newSpot = (GeneralPath)spot.clone();
newSpot.transform(java.awt.geom.AffineTransform.getScaleInstance(sx,sy));
spot = newSpot;
break;
}
bounds = getBounds();
}
public Rectangle2D getBounds2D(){
if(spot != null) return spot.getBounds2D();
return null;
}
public java.awt.geom.PathIterator getPathIterator(java.awt.geom.AffineTransform at){
if(spot != null) return spot.getPathIterator(at);
return null;
}
public java.awt.geom.PathIterator getPathIterator(java.awt.geom.AffineTransform at,double flatness){
if(spot != null) return spot.getPathIterator(at,flatness);
return null;
}
public boolean intersects(Rectangle2D r2d){
if(r2d == null || spot == null) return false;
return spot.intersects(r2d);
}
public boolean intersects(double x, double y,double w,double h){
if(spot == null) return false;
return spot.intersects(x,y,w,h);
}
protected void normalize(){
if(spot == null) return;
java.awt.geom.PathIterator pit = spot.getPathIterator(null);
double []tempArray = new double[6];
Vector ptsv = new Vector();
while(!pit.isDone()){
int type = pit.currentSegment(tempArray);
if(type == java.awt.geom.PathIterator.SEG_MOVETO || type == java.awt.geom.PathIterator.SEG_LINETO){
ptsv.add(new Point((int)Math.round(tempArray[0]),(int)Math.round(tempArray[1])));
}
pit.next();
}
Point []pts = new Point[ptsv.size()];
for(int i = 0; i < pts.length; i++){
pts[i] = (Point)ptsv.elementAt(i);
}
QuickHull qh = new QuickHull(pts);
java.awt.Polygon pol = new java.awt.Polygon();
for(int i = 0; i < qh.hullPoints.size(); i++){
Point pt = (Point)qh.hullPoints.elementAt(i);
pol.addPoint(pt.x,pt.y);
}
spot.reset();
spot.append(new GeneralPath(pol),true);
}
void recreateSpotWithPickedLine(Point pt){
if(pickedPoints.size() < 1 || pickedPointIndex < 0) return;
spot.reset();
pickedPoints.remove(pickedPointIndex);
pickedPoints.insertElementAt(pt,pickedPointIndex);
for(int i = 1; i < pickedPoints.size(); i++){
java.awt.geom.Line2D.Double line = new java.awt.geom.Line2D.Double((Point)pickedPoints.elementAt(i-1),(Point)pickedPoints.elementAt(i));
spot.append(line,true);
}
}
protected int checkClickOnLine(Point pt,Vector ptsv){
int pointIndex = -1;
if(pt == null || isEmpty() || ptsv == null ) return pointIndex;
ptsv.removeAllElements();
java.awt.geom.PathIterator pit = spot.getPathIterator(null);
double []tempArray = new double[6];
while(!pit.isDone()){
int type = pit.currentSegment(tempArray);
if(type == java.awt.geom.PathIterator.SEG_MOVETO || type == java.awt.geom.PathIterator.SEG_LINETO){
ptsv.add(new Point((int)Math.round(tempArray[0]),(int)Math.round(tempArray[1])));
}
pit.next();
}
int nPoints = ptsv.size();
Point2D.Double p2d = new Point2D.Double(pt.x,pt.y);
if(nPoints >= 2){
Point ptS = (Point)ptsv.elementAt(0);
Point ptE = (Point)ptsv.elementAt(nPoints - 1);
if(!ptS.equals(ptE)){
ptsv.add(ptS);
nPoints++;
}
for(int i = 1; i < nPoints; i++){
java.awt.geom.Line2D.Double line = new java.awt.geom.Line2D.Double((Point)ptsv.elementAt(i-1),(Point)ptsv.elementAt(i));
if(p2d.distance(line.getP1()) < 3){
pointIndex = i - 1;
}else if(p2d.distance(line.getP2()) < 3){
pointIndex = i;
}else if(line.ptSegDist(p2d) < 3){
ptsv.insertElementAt(pt,i);
pointIndex = i;
}
if(pointIndex >= 0) break;
}
}
return pointIndex;
}
protected void prepareForSerialization(){
if(spot == null) return;
java.awt.geom.PathIterator pit = spot.getPathIterator(null);
double []tempArray = new double[6];
Vector ptsv = new Vector();
while(!pit.isDone()){
int type = pit.currentSegment(tempArray);
if(type == java.awt.geom.PathIterator.SEG_MOVETO || type == java.awt.geom.PathIterator.SEG_LINETO){
ptsv.add(new Point2D.Double(tempArray[0],tempArray[1]));
}
pit.next();
}
points = new Point2D.Double[ptsv.size()];
for(int i = 0; i < points.length; i++){
points[i] = (Point2D)ptsv.elementAt(i);
}
}
public static class AnnotationSpotState implements java.io.Externalizable{
static final long serialVersionUID = -3924729488905394223L;
int index;
int choosingMode;
String annotation;
Point annotationToolTipLocation;
Rectangle bounds;
java.awt.Color spotColor;
boolean toolTipMode;
Point2D []points;
public int getIndex(){return index;}
public void setIndex(int index){this.index = index;}
public int getChoosingMode(){return choosingMode;}
public void setChoosingMode(int choosingMode){this.choosingMode = choosingMode;}
public String getAnnotation(){return annotation;}
public void setAnnotation(String annotation){this.annotation = annotation;}
public Point getAnnotationToolTipLocation(){return annotationToolTipLocation;}
public void setAnnotationToolTipLocation(Point annotationToolTipLocation){this.annotationToolTipLocation = annotationToolTipLocation;}
public Rectangle getBounds(){return bounds;}
public void setBounds(Rectangle bounds){this.bounds = bounds;}
public java.awt.Color getSpotColor(){return spotColor;}
public void setSpotColor(java.awt.Color spotColor){this.spotColor = spotColor;}
public boolean isToolTipMode(){return toolTipMode;}
public void setToolTipMode(boolean toolTipMode){this.toolTipMode = toolTipMode;}
public Point2D []getPoints(){return points;}
public void setPoints(Point2D []points){this.points = (Point2D [])points.clone();}
public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException{
out.writeInt(index);
out.writeInt(choosingMode);
out.writeUTF(annotation);
out.writeObject(annotationToolTipLocation);
out.writeObject(bounds);
out.writeObject(spotColor);
out.writeBoolean(toolTipMode);
if(points == null){
out.writeInt(-1);
}else{
out.writeInt(points.length);
for(int i = 0; i < points.length; i++){
Point2D pt = points[i];
out.writeDouble(pt.getX());
out.writeDouble(pt.getY());
}
}
}
public void readExternal(java.io.ObjectInput in) throws java.io.IOException, ClassNotFoundException{
index = in.readInt();
choosingMode = in.readInt();
annotation = in.readUTF();
annotationToolTipLocation = (Point)in.readObject();
bounds = (Rectangle)in.readObject();
spotColor = (java.awt.Color)in.readObject();
toolTipMode = in.readBoolean();
int nPoints = in.readInt();
points = null;
if(nPoints >= 0){
points = new Point2D[nPoints];
for(int i = 0; i < nPoints; i++){
double x = in.readDouble();
double y = in.readDouble();
points[i] = new Point2D.Double(x,y);
}
}
}
}
protected void setAnnotationFromPopUp(JPopupMenu pm){
if(pm == null) return;
java.awt.Component jc = pm.getComponent(0);
if(!(jc instanceof javax.swing.JScrollPane)) return;
javax.swing.JScrollPane sp = (javax.swing.JScrollPane)jc;
java.awt.Component view = sp.getViewport().getView();
if(!(view instanceof javax.swing.JTextArea)) return;
setAnnotation(((javax.swing.JTextArea)view).getText());
textAreaMenu = null;
}
protected void setupPopupMenu(){
if(isEmpty() || annotationImage == null || !annotationImage.isEditMode()) return;
textAreaMenu = new JPopupMenu();
textAreaMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener(){
public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent e){
setPopupVisible(true);
}
public void popupMenuCanceled(javax.swing.event.PopupMenuEvent e){
}
public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent e){
setPopupVisible(false);
final Object obj = e.getSource();
if(!(obj instanceof JPopupMenu)) return;
JPopupMenu pm = (JPopupMenu)obj;
setAnnotationFromPopUp((JPopupMenu)obj);
}
});
textAreaMenu.setBackground(new java.awt.Color(200, 200, 200));
textAreaMenu.setOpaque(true);
textArea.setText(getAnnotation());
javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane(textArea);
scrollPane.setPreferredSize(new java.awt.Dimension(200,200));
textAreaMenu.add(scrollPane);
textAreaMenu.pack();
Point ptMenu = annotationToolTipLocation;
if(ptMenu == null){
ptMenu = new Point(getBounds().x, getBounds().y);
}else{
ptMenu = javax.swing.SwingUtilities.convertPoint(annotationImage.getParent(),ptMenu,annotationImage);
}
textAreaMenu.show(annotationImage, ptMenu.x, ptMenu.y);
textArea.requestFocus();
}
}
class TranslucentGlassOp implements java.awt.image.BufferedImageOp{
int x;
int y;
int w=-1;
int h=-1;
TranslucentGlassOp(int x, int y, int w, int h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public synchronized BufferedImage filter(BufferedImage src, BufferedImage dest){
if(dest == null) dest = createCompatibleDestImage(src,null);
int iw = src.getWidth();
int ih = src.getHeight();
for(int ix = 0; ix < iw; ix++){
for(int iy = 0; iy < ih; iy++){
int px = src.getRGB(ix,iy);
if(ix < x || ix >= x+w || iy < y || iy >= y+h){
px = 0x44000000 | (px & 0xFFFFFF);
}
dest.setRGB(ix,iy,px);
}
}
return dest;
}
public java.awt.geom.Rectangle2D getBounds2D (BufferedImage src){
return src.getRaster().getBounds();
}
public BufferedImage createCompatibleDestImage (BufferedImage src,java.awt.image.ColorModel destCM){
if(destCM == null){
destCM = src.getColorModel();
if (destCM instanceof java.awt.image.IndexColorModel) {
destCM = java.awt.image.ColorModel.getRGBdefault();
}
}
int w = src.getWidth();
int h = src.getHeight();
return new BufferedImage(destCM,destCM.createCompatibleWritableRaster(w,h),destCM.isAlphaPremultiplied(),null);
}
public Point2D getPoint2D (Point2D srcPt, Point2D dstPt){
if(dstPt == null) dstPt = new Point2D.Float();
dstPt.setLocation(srcPt);
return dstPt;
}
public java.awt.RenderingHints getRenderingHints(){return null;}
public void setX(int x){this.x = x;}
public void setY(int y){this.y = y;}
public void setW(int w){this.w = w;}
public void setH(int h){this.h = h;}
public int getX(){return x;}
public int getY(){return y;}
public int getW(){return w;}
public int getH(){return h;}
}
}
class StaticAnnotationToolTip extends JTextPane{
JAnnotationImageModel.AnnotationSpot owner;
boolean needResize = false;
StaticAnnotationToolTip(boolean htmlSupport){
super();
setBorder(javax.swing.BorderFactory.createEtchedBorder(javax.swing.border.EtchedBorder.RAISED));
setBackground(java.awt.Color.yellow);
if(htmlSupport){
setContentType("text/html");
}else{
setContentType("text/plain");
}
setEditable(false);
setEnabled(false);
addMouseListener(new java.awt.event.MouseAdapter(){
public void mouseReleased(java.awt.event.MouseEvent evt){
if(evt.getClickCount() == 2){
if(owner != null) owner.setupPopupMenu();
}
}
});
}
public void setText(String text){
super.setText(text);
setSize(getPreferredSize());
repaint();
needResize = true;
}
public void paint(java.awt.Graphics g){
if(needResize){
needResize = false;
setSize(getPreferredSize());
}
super.paint(g);
}
public void setOwner(JAnnotationImageModel.AnnotationSpot owner){
this.owner = owner;
}
public JAnnotationImageModel.AnnotationSpot getOwner(){return owner;}
}