package org.archstudio.bna.logics.coordinating;
import static com.google.common.base.Preconditions.checkNotNull;
import java.awt.Shape;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import java.util.Set;
import org.archstudio.bna.IBNAModel;
import org.archstudio.bna.IBNAModelListener;
import org.archstudio.bna.IBNAWorld;
import org.archstudio.bna.IThing;
import org.archstudio.bna.ThingEvent;
import org.archstudio.bna.constants.StickyMode;
import org.archstudio.bna.facets.IHasEndpoints;
import org.archstudio.bna.facets.IHasMutableLoopOrientation;
import org.archstudio.bna.facets.IHasStickyShape;
import org.archstudio.bna.keys.IThingKey;
import org.archstudio.bna.logics.AbstractCoordinatingThingLogic;
import org.archstudio.bna.logics.coordinating.StickPointLogic.PointUpdater;
import org.archstudio.bna.utils.BNAUtils;
import org.archstudio.swtutils.constants.Orientation;
import org.archstudio.sysutils.SystemUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
@NonNullByDefault
public class StickPointLogic extends AbstractCoordinatingThingLogic<PointUpdater> implements IBNAModelListener {
public boolean DEBUG = false;
@NonNullByDefault
public static interface IHasSecondaryPoint extends IThing {
public Point2D getSecondaryPoint(IBNAModel model, StickPointLogic stickLogic, IThingKey<Point2D> pointKey);
}
@NonNullByDefault
public static interface IHasLoopablePoint extends IHasMutableLoopOrientation {
public Orientation getLoopOrientation(IBNAModel model, StickPointLogic stickLogic, IThingKey<Point2D> pointKey);
}
@NonNullByDefault
public static interface IStuckThing {
public IThing getStuckThing();
public IThingKey<Point2D> getStuckPointKey();
}
public class PointUpdater extends AbstractCoordinatingThingLogic.Updater implements IStuckThing {
final Object pointThingID;
final IThingKey<Point2D> pointKey;
final StickyMode stickyMode;
final Object stickyThingID;
Shape stickyShape;
boolean updating;
public PointUpdater(IThing pointThing, IThingKey<Point2D> pointKey, StickyMode stickyMode,
IHasStickyShape stickyThing) {
this.pointThingID = pointThing.getID();
this.pointKey = pointKey;
this.stickyMode = stickyMode;
this.stickyThingID = stickyThing.getID();
this.stickyShape = stickyThing.getStickyShape();
}
@Override
public IThing getStuckThing() {
IThing thing = model.getThing(pointThingID);
if (thing == null) {
throw new NullPointerException();
}
return thing;
}
@Override
public IThingKey<Point2D> getStuckPointKey() {
return pointKey;
}
@Override
public void update(ThingEvent event) {
if (updating) {
return;
}
if (event != null) {
if (event.getTargetThing().getID().equals(stickyThingID)) {
if (!event.getTargetThing().isShapeModifyingKey(event.getPropertyName())) {
return;
}
}
else {
if (!event.getTargetThing().isShapeModifyingKey(event.getPropertyName())) {
return;
}
}
}
IThing pointThing = model.getThing(pointThingID);
if (pointThing != null) {
IHasStickyShape stickyThing = SystemUtils.castOrNull(model.getThing(stickyThingID),
IHasStickyShape.class);
if (stickyThing != null) {
if (DEBUG) {
System.err.println(" " + pointKey + " -> " + SystemUtils.simpleName(pointThing.getClass())
+ "(" + pointThing.getID() + "):" + pointThing.get(pointKey) + " " + stickyMode + " "
+ SystemUtils.simpleName(stickyThing.getClass()) + "(" + stickyThing.getID() + "):"
+ stickyThing.getStickyShape());
}
Point2D stuckPoint = pointThing.get(pointKey);
if (stuckPoint == null) {
stuckPoint = new Point2D.Double(0, 0);
}
// adjust the point proportionally if the 'stickyThing' has
// resized/moved
Shape newStickyShape = stickyThing.getStickyShape();
if (!newStickyShape.equals(stickyShape)) {
Rectangle2D from = stickyShape.getBounds2D();
Rectangle2D to = newStickyShape.getBounds2D();
double x = (stuckPoint.getX() - from.getMinX()) * to.getWidth() / from.getWidth()
+ to.getMinX();
double y = (stuckPoint.getY() - from.getMinY()) * to.getHeight() / from.getHeight()
+ to.getMinY();
stuckPoint.setLocation(x, y);
stickyShape = newStickyShape;
}
switch (stickyMode) {
case CENTER: {
// get center point
Rectangle2D r = stickyShape.getBounds();
stuckPoint.setLocation(r.getCenterX(), r.getCenterY());
}
break;
case EDGE: {
// calculate the closest point on the sticky shape,
// given the current point as reference
stuckPoint = BNAUtils.getClosestPointOnShape(stickyShape, stuckPoint.getX(), stuckPoint.getY());
}
break;
case EDGE_FROM_CENTER: {
IHasSecondaryPoint secondaryPointThing = (IHasSecondaryPoint) pointThing;
// get center point
{
Rectangle2D r = stickyShape.getBounds2D();
stuckPoint.setLocation(r.getCenterX(), r.getCenterY());
}
// get secondary point ...
Point2D secondaryPoint;
// ... check for loop
if (pointThing instanceof IHasLoopablePoint) {
Orientation loopOrientation = ((IHasLoopablePoint) pointThing).getLoopOrientation(model,
StickPointLogic.this, pointKey);
if (loopOrientation != Orientation.NONE) {
// determine point offsets
Rectangle r = BNAUtils.toRectangle(stickyShape.getBounds());
double preferred = 30;
double x, dx = Math.min(preferred, r.width / 3);
double y, dy = Math.min(preferred, r.height / 3);
double x1 = r.x, x2 = r.x + r.width, xm = (x1 + x2) / 2;
double y1 = r.y, y2 = r.y + r.height, ym = (y1 + y2) / 2;
if (pointKey.equals(IHasEndpoints.ENDPOINT_1_KEY)) {
switch (loopOrientation) {
case NORTHWEST:
x = x1;
y = y1 + dy;
break;
case NORTH:
x = xm - dx * 2 / 3;
y = y1;
break;
case NORTHEAST:
x = x2 - dx;
y = y1;
break;
case WEST:
x = x1;
y = ym + dy * 2 / 3;
break;
case EAST:
x = x2;
y = ym - dy * 2 / 3;
break;
case SOUTHWEST:
x = x1 + dx;
y = y2;
break;
case SOUTH:
x = xm + dx * 2 / 3;
y = y2;
break;
case SOUTHEAST:
x = x2;
y = y2 - dy;
break;
default:
throw new IllegalArgumentException(loopOrientation.toString());
}
}
else if (pointKey.equals(IHasEndpoints.ENDPOINT_2_KEY)) {
switch (loopOrientation) {
case NORTHWEST:
x = x1 + dx;
y = y1;
break;
case NORTH:
x = xm + dx * 2 / 3;
y = y1;
break;
case NORTHEAST:
x = x2;
y = y1 + dy;
break;
case WEST:
x = x1;
y = ym - dy * 2 / 3;
break;
case EAST:
x = x2;
y = ym + dy * 2 / 3;
break;
case SOUTHWEST:
x = x1;
y = y2 - dy;
break;
case SOUTH:
x = xm - dx * 2 / 3;
y = y2;
break;
case SOUTHEAST:
x = x2 - dx;
y = y2;
break;
default:
throw new IllegalArgumentException(loopOrientation.toString());
}
}
else {
throw new IllegalArgumentException(pointKey.toString());
}
secondaryPoint = new Point2D.Double(x, y);
}
else {
// get secondary point
secondaryPoint = secondaryPointThing.getSecondaryPoint(model, StickPointLogic.this,
pointKey);
}
((IHasLoopablePoint) pointThing).setLoopOrientation(loopOrientation);
}
else {
// get secondary point
secondaryPoint = secondaryPointThing.getSecondaryPoint(model, StickPointLogic.this,
pointKey);
if (pointThing instanceof IHasLoopablePoint) {
((IHasLoopablePoint) pointThing).setLoopOrientation(Orientation.NONE);
}
}
// calculate the closest point on the sticky shape,
// given the current point as reference
stuckPoint = BNAUtils.getClosestPointOnShape(stickyShape, secondaryPoint.getX(),
secondaryPoint.getY(), stuckPoint.getX(), stuckPoint.getY());
}
break;
}
// update the actual stuck point
Point2D oldStuckPoint;
updating = true;
try {
oldStuckPoint = pointThing.set(pointKey, stuckPoint);
}
finally {
updating = false;
}
if (DEBUG) {
if (!BNAUtils.like(stuckPoint, oldStuckPoint)) {
System.err.println(" -- " + oldStuckPoint + " -> " + stuckPoint);
}
}
}
}
}
}
public StickPointLogic(IBNAWorld world) {
super(world);
}
public void stick(IThing pointThing, IThingKey<Point2D> pointKey, StickyMode stickyMode, IHasStickyShape stickyThing) {
checkNotNull(pointThing);
checkNotNull(pointKey);
checkNotNull(stickyMode);
checkNotNull(stickyThing);
BNAUtils.checkLock();
for (PointUpdater oldUpdater : getRegisteredUpdater(pointThing, pointKey)) {
if (oldUpdater != null && oldUpdater.stickyMode == stickyMode
&& oldUpdater.stickyThingID.equals(stickyThing.getID())) {
return;
}
}
unregister(pointThing, pointKey);
if (stickyMode != null && stickyThing != null) {
PointUpdater updater = new PointUpdater(pointThing, pointKey, stickyMode, stickyThing);
register(updater, pointThing, pointKey);
removeWithThing(updater, pointThing, stickyThing);
track(updater, pointThing, stickyThing);
}
else {
unstick(pointThing, pointKey);
}
}
public void unstick(IThing pointThing, IThingKey<Point2D> pointKey) {
checkNotNull(pointThing);
checkNotNull(pointKey);
BNAUtils.checkLock();
unregister(pointThing, pointKey);
}
public @Nullable
StickyMode getStickyMode(IThing pointThing, IThingKey<Point> pointKey) {
checkNotNull(pointThing);
checkNotNull(pointKey);
BNAUtils.checkLock();
for (PointUpdater updater : getRegisteredUpdater(pointThing, pointKey)) {
return updater.stickyMode;
}
return null;
}
public @Nullable
Object getStickyThingID(IThing pointThing, IThingKey<Point2D> pointKey) {
checkNotNull(pointThing);
checkNotNull(pointKey);
BNAUtils.checkLock();
for (PointUpdater updater : getRegisteredUpdater(pointThing, pointKey)) {
return updater.stickyThingID;
}
return null;
}
public @Nullable
IHasStickyShape getStickyThing(IThing pointThing, IThingKey<Point2D> pointKey) {
checkNotNull(pointThing);
checkNotNull(pointKey);
BNAUtils.checkLock();
for (PointUpdater updater : getRegisteredUpdater(pointThing, pointKey)) {
return SystemUtils.castOrNull(model.getThing(updater.stickyThingID), IHasStickyShape.class);
}
return null;
}
public Point2D getStuckPoint(IThing pointThing, IThingKey<Point2D> pointKey) {
return getStuckPoint(pointThing, pointKey, pointThing.get(pointKey));
}
public Point2D getStuckPoint(IThing pointThing, IThingKey<Point2D> pointKey, Point2D stuckPoint) {
checkNotNull(pointThing);
checkNotNull(pointKey);
checkNotNull(stuckPoint);
BNAUtils.checkLock();
/*
* Check to see if the point is stuck to something, and if stuck to the center point of that thing, return the
* center point rather than the secondary point as this will be more accurate
*/
for (PointUpdater updater : getRegisteredUpdater(pointThing, pointKey)) {
if (updater.stickyMode.isStuckToCenterPoint()) {
IHasStickyShape stickyThing = SystemUtils.castOrNull(model.getThing(updater.stickyThingID),
IHasStickyShape.class);
if (stickyThing != null) {
// if so, get the center point of what it's stuck to
return BNAUtils.getCentralPoint(stickyThing);
}
}
}
return stuckPoint;
}
public List<IStuckThing> getStuckThings(IHasStickyShape stickyThing) {
return Lists.<IStuckThing> newArrayList(getUpdatersTrackingThing(stickyThing.getID()));
}
public boolean isLoopingPoint(IThing pointThing, IThingKey<Point2D> endpoint1Key, IThingKey<Point2D> endpoint2Key) {
checkNotNull(pointThing);
checkNotNull(endpoint1Key);
checkNotNull(endpoint2Key);
BNAUtils.checkLock();
Set<Object> stickyThingID = Sets.newHashSet();
for (PointUpdater updater : getRegisteredUpdater(pointThing, endpoint1Key)) {
stickyThingID.add(updater.stickyThingID);
}
for (PointUpdater updater : getRegisteredUpdater(pointThing, endpoint2Key)) {
if (stickyThingID.contains(updater.stickyThingID)) {
return true;
}
}
return false;
}
}