blob: 79c6d4c3cfbef9c22c1f8a9694de5557b8f42abe [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2004, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
****************************************************************************/
package org.eclipse.gmf.runtime.gef.ui.figures;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import org.eclipse.draw2d.AnchorListener;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.IImageFigure;
import org.eclipse.draw2d.IImageFigure.ImageChangedListener;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Ray;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.PointListUtilities;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
/**
* Implements sliding connection anchor functionality for Image figures
*
* @author aboyko
*
*/
public class SlidableImageAnchor
extends SlidableAnchor implements ImageChangedListener {
static private class ImageAnchorLocation {
static private Map<Image, ImageAnchorLocation> imageAnchorLocationMap = new WeakHashMap<Image, ImageAnchorLocation>();
/**
* getInstance Static method for returning an instance of the
* ImageAnchorLocation object corresponding to the given Image.
*
* @param image
* Image to determine anchor location for
* @return ImageAnchorLocation containing cached information about
* anchor
*/
static ImageAnchorLocation getInstance(Image image) {
ImageAnchorLocation imgAnchorLoc = imageAnchorLocationMap
.get(image);
if (imgAnchorLoc == null) {
imgAnchorLoc = new ImageAnchorLocation(image);
imageAnchorLocationMap.put(image, imgAnchorLoc);
}
return imgAnchorLoc;
}
private Map<Integer, Point> locationMap = new HashMap<Integer, Point>();
private ImageData imgData = null;
private ImageData transMaskData = null;
private ImageAnchorLocation(Image img) {
imgData = img.getImageData();
transMaskData = imgData.getTransparencyMask();
}
/**
* @return Returns the imgData.
*/
protected ImageData getImageData() {
return imgData;
}
/**
* @return Returns the transMaskData.
*/
protected ImageData getTransparencyMaskData() {
return transMaskData;
}
/**
* isTransparentAt Accessor to determine if the image is transparent at
* a given point.
*
* @param x
* int location into the image
* @param y
* int location into the image
* @param checkAdjacent
* check adjacent pixels for transparency as well.
* @return boolean true if transparent, false otherwise.
*/
protected boolean isTransparentAt(int x, int y, boolean checkAdjacent) {
// boundary checking
if (x < 0 || x >= getImageData().width || y < 0
|| y >= getImageData().height)
return true;
// check for alpha channel
int transValue = 255;
// check for transparency mask
if (getTransparencyMaskData() != null) {
transValue = getTransparencyMaskData().getPixel(x, y) == 0 ? 0
: 255;
}
if (transValue != 0) {
if (getImageData().alphaData != null) {
transValue = getImageData().getAlpha(x, y);
}
}
// use a tolerance
boolean trans = false;
if (transValue < 10) {
trans = true;
if (checkAdjacent) {
trans &= isTransparentAt(x + 1, y, false);
trans &= isTransparentAt(x + 1, y + 1, false);
trans &= isTransparentAt(x + 1, y - 1, false);
trans &= isTransparentAt(x - 1, y + 1, false);
trans &= isTransparentAt(x - 1, y, false);
trans &= isTransparentAt(x - 1, y - 1, false);
trans &= isTransparentAt(x, y + 1, false);
trans &= isTransparentAt(x, y - 1, false);
}
}
return trans;
}
/**
* getLocation Delegation function used by the ConnectionAnchor
* getLocation
*
* @param start the <code>Point</code> that is the beginning of a line segment used to
* calculate the anchor location inside the image.
* @param edge the <code>Point</code> that is the end of a line segment used to
* calculate the anchor location inside the image.
* @param isDefaultAnchor - true if location for the default anchor should be calculated
* @return Point representing the location inside the image to anchor
* to.
*/
private Point getLocation(Point start, Point edge, Rectangle containerRect, boolean isDefaultAnchor) {
int angle = calculateAngleOfEntry(start, edge);
Point top = containerRect.getTopLeft();
Point ptIntersect = null;
// Default anchors are cached
if (isDefaultAnchor) {
// determine if a cached value exists
ptIntersect = locationMap.get(new Integer(angle));
}
if (ptIntersect == null) {
// if no cached value exists return the calculated value and add to
// the map
Dimension dim = edge.getDifference(top);
Point edgeImg = new Point(Math.max(0, Math.min(dim.width,
getImageData().width - 1)), Math.max(0, Math.min(dim.height,
getImageData().height - 1)));
Dimension startDim = start.getDifference(top);
Point startImg = new Point(Math.max(0, Math.min(startDim.width,
getImageData().width - 1)), Math.max(0, Math.min(
startDim.height, getImageData().height - 1)));
ptIntersect = calculateIntersection(startImg, edgeImg);
if (ptIntersect == null)
return null;
if (isDefaultAnchor) {
locationMap.put(Integer.valueOf(angle), ptIntersect);
}
}
return ptIntersect.getTranslated(top.x, top.y);
}
/**
* calculateAngleOfEntry Utility method to calculate the angle of entry
*
* @param start
* @param edge
* @return int angle in degrees rounded to 15% for use as a key to a
* map.
*/
private int calculateAngleOfEntry(Point start, Point edge) {
LineSeg lineSeg = new LineSeg(start, edge);
Ray ray = new Ray(lineSeg.getOrigin(), new Point(lineSeg
.getOrigin().x + 1, lineSeg.getOrigin().y));
double angle = 0.0;
LineSeg.TrigValues trig = lineSeg.getTrigValues(ray);
if (trig != null)
angle = Math.atan2(-trig.sinTheta, -trig.cosTheta) + Math.PI;
int keyAngle = (int) Math.round(angle * 360 / (Math.PI * 2));
return keyAngle - (keyAngle % 10);
}
/**
* calculateIntersection Utility method to calculate the intersection
* point of a given point at an angle into the image to find the first
* opaque pixel.
*
* @param start
* Point that is in the center of the Image.
* @param edge
* Point that is on the edge of the Image.
* @return Point that is the intersection with the first opaque pixel.
*/
private Point calculateIntersection(Point start, Point edge) {
Point opaque = new Point(edge);
LineSeg line = new LineSeg(start, edge);
long distance = Math.round(line.length());
// otherwise calculate value
while (opaque.x >= 0 && opaque.x < getImageData().width
&& opaque.y >= 0 && opaque.y < getImageData().height) {
if (!isTransparentAt(opaque.x, opaque.y, true)) {
return opaque;
}
line.pointOn(distance, LineSeg.KeyPoint.ORIGIN, opaque);
distance--;
}
// default is to fall through and return the chopbox point
return null;
}
}
private IImageFigure imageFig;
/**
* Empty constructor
*/
public SlidableImageAnchor() {
super();
}
/**
* Dumb default constructor, for which reference point is at the center of the figure
* @param f the <code>IFigure</code> bounding figure
*/
public SlidableImageAnchor(IFigure f) {
super(f);
if (f instanceof IImageFigure) {
this.imageFig = (IImageFigure) f;
}
}
/**
* Default constructor, for which reference point is at the cneter of the figure
*
* @param container the <code>IFigure</code> bounding figure
* @param imageFig the <code>ImageFigure</code> inside the bounding figure
*/
public SlidableImageAnchor(IFigure container, IImageFigure imageFig) {
super(container);
this.imageFig = imageFig;
}
@Override
public void addAnchorListener(AnchorListener listener) {
if (listener == null)
return;
if (listeners.isEmpty() && imageFig != null) {
imageFig.addImageChangedListener(this);
}
super.addAnchorListener(listener);
}
@Override
public void removeAnchorListener(AnchorListener listener) {
super.removeAnchorListener(listener);
if (listeners.isEmpty() && imageFig != null) {
imageFig.removeImageChangedListener(this);
}
}
/**
* Constructor, for which reference point is specified
*
* @param f the <code>IFigure</code> bounding figure
* @param imageFig the <code>ImageFigure</code> inside the bounding figure
* @param p the <code>PrecisionPoint</code> relative reference
*/
public SlidableImageAnchor(IFigure f, IImageFigure imageFig, PrecisionPoint p) {
super(f, p);
this.imageFig = imageFig;
}
/**
* Returns the image.
*
* @return the <code>Image</code> object
*/
protected Image getImage() {
return imageFig.getImage();
}
/**
* Returns bounds of the figure.
*
* @return the owner figure
*/
protected IFigure getContainer() {
return getOwner();
}
/* (non-Javadoc)
* @see org.eclipse.gmf.runtime.draw2d.ui.figures.BaseSlidableAnchor#getLocation(org.eclipse.draw2d.geometry.Point, org.eclipse.draw2d.geometry.Point)
*/
protected Point getLocation(Point ownReference, Point foreignReference) {
Image image = getImage();
if (image == null)
return super.getLocation(ownReference, foreignReference);
Rectangle ownerRect = getBox();
PointList intersections = getIntersectionPoints(ownReference,
foreignReference);
if (intersections != null && intersections.size() != 0) {
Point ptRef = PointListUtilities.pickFarestPoint(intersections,
foreignReference);
Point ptEdge = PointListUtilities.pickClosestPoint(intersections,
foreignReference);
Point location = ImageAnchorLocation.getInstance(getImage())
.getLocation(ptRef, ptEdge, ownerRect,
getReferencePoint().equals(ownReference) && isDefaultAnchor());
if (location != null) {
location = normalizeToStraightlineTolerance(foreignReference,
location, 3);
}
return location;
}
return null;
}
/**
* @since 1.4
*/
public void imageChanged() {
fireAnchorMoved();
}
}