// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/omGraphics/EditableOMCircle.java,v $
// $RCSfile: EditableOMCircle.java,v $
// $Revision: 1.12 $
// $Date: 2009/01/21 01:24:41 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.omGraphics;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import com.bbn.openmap.omGraphics.editable.CircleStateMachine;
import com.bbn.openmap.omGraphics.editable.GraphicEditState;
import com.bbn.openmap.omGraphics.editable.GraphicSelectedState;
import com.bbn.openmap.omGraphics.editable.GraphicSetOffsetState;
import com.bbn.openmap.proj.DrawUtil;
import com.bbn.openmap.proj.GeoProj;
import com.bbn.openmap.proj.Length;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.stateMachine.State;
/**
* The EditableOMCircle encompasses an OMCircle, providing methods for modifying
* or creating it. This class only modifies circles in lat/lon space
* (RENDERTYPE_LATLON) - and ellipses in screen space (RENDERTYPE_XY or
* RENDERTYPE_OFFSET). When you grab at the circle, you change the radius of the
* entire circle. Grabbing the center point moves the circle. If there is an
* offset point, moving the center point changes the circle's position in
* relation to the offset point. Moving the offset point moves the circle,
* keeping the distance to the center point constant.
*/
public class EditableOMCircle extends EditableOMGraphic {
protected VerticalGrabPoint gpn;
protected HorizontalGrabPoint gpw;
protected VerticalGrabPoint gps;
protected HorizontalGrabPoint gpe;
protected GrabPoint gpnw;
protected GrabPoint gpne;
protected GrabPoint gpsw;
protected GrabPoint gpse;
protected OffsetGrabPoint gpc;
protected GrabPoint gpr;
protected OffsetGrabPoint gpo; // offset
protected OffsetGrabPoint gpm; // for grabbing the circle and
// changing the radius during creation.
protected OMCircle circle;
public final static int CENTER_POINT_INDEX = 0;
public final static int NW_POINT_INDEX = 1;
public final static int N_POINT_INDEX = 2;
public final static int NE_POINT_INDEX = 3;
public final static int W_POINT_INDEX = 4;
public final static int E_POINT_INDEX = 5;
public final static int SW_POINT_INDEX = 6;
public final static int S_POINT_INDEX = 7;
public final static int SE_POINT_INDEX = 8;
public final static int RADIUS_POINT_INDEX = 9;
public final static int OFFSET_POINT_INDEX = 10;
/**
* Create the EditableOMCircle, setting the state machine to create the
* circle off of the gestures.
*/
public EditableOMCircle() {
createGraphic(null);
}
/**
* Create an EditableOMCircle with the circleType and renderType parameters
* in the GraphicAttributes object.
*/
public EditableOMCircle(GraphicAttributes ga) {
createGraphic(ga);
}
/**
* Create the EditableOMCircle with an OMCircle already defined, ready for
* editing.
*
* @param omc OMCircle that should be edited.
*/
public EditableOMCircle(OMCircle omc) {
setGraphic(omc);
}
/**
* Create and initialize the state machine that interprets the modifying
* gestures/commands, as well as initialize the grab points. Also allocates
* the grab point array needed by the EditableOMCircle.
*/
public void init() {
setCanGrabGraphic(false);
Debug.message("eomg", "EditableOMCircle.init()");
setStateMachine(new CircleStateMachine(this));
gPoints = new GrabPoint[11];
}
/**
* Set the graphic within the state machine. If the graphic is null, then
* one shall be created, and located off screen until the gestures driving
* the state machine place it on the map.
*/
public void setGraphic(OMGraphic graphic) {
init();
if (graphic instanceof OMCircle) {
circle = (OMCircle) graphic;
stateMachine.setSelected();
setGrabPoints(circle);
} else {
createGraphic(null);
}
}
/**
* Create and set the graphic within the state machine. The
* GraphicAttributes describe the type of circle to create.
*/
public void createGraphic(GraphicAttributes ga) {
init();
stateMachine.setUndefined();
int renderType = OMGraphic.RENDERTYPE_UNKNOWN;
if (ga != null) {
renderType = ga.getRenderType();
}
if (Debug.debugging("eomc")) {
Debug.output("EditableOMCircle.createGraphic(): rendertype = " + renderType);
}
switch (renderType) {
case (OMGraphic.RENDERTYPE_LATLON):
circle = new OMCircle(90f, -180f, 0f);
break;
case (OMGraphic.RENDERTYPE_OFFSET):
circle = new OMCircle(90f, -180f, 0, 0, 1, 1);
break;
default:
circle = new OMCircle(-1, -1, 1, 1);
}
if (ga != null) {
ga.setTo(circle, true);
}
}
/**
* Get whether a graphic can be manipulated by its edges, rather than just
* by its grab points.
*/
public boolean getCanGrabGraphic() {
return canGrabGraphic || circle.renderType == OMGraphic.RENDERTYPE_LATLON;
}
/**
* Get the OMGraphic being created/modified by the EditableOMCircle.
*/
public OMGraphic getGraphic() {
return circle;
}
/**
* Attach to the Moving OffsetGrabPoint so if it moves, it will move this
* EditableOMGraphic with it. EditableOMGraphic version doesn't do anything,
* each subclass has to decide which of its OffsetGrabPoints should be
* attached to it.
*/
public void attachToMovingGrabPoint(OffsetGrabPoint gp) {
gp.addGrabPoint(gpo);
}
/**
* Detach from a Moving OffsetGrabPoint. The EditableOMGraphic version
* doesn't do anything, each subclass should remove whatever GrabPoint it
* would have attached to an OffsetGrabPoint.
*/
public void detachFromMovingGrabPoint(OffsetGrabPoint gp) {
gp.removeGrabPoint(gpo);
}
/**
* Set the GrabPoint that is in the middle of being modified, as a result of
* a mouseDragged event, or other selection process.
*/
public void setMovingPoint(GrabPoint gp) {
super.setMovingPoint(gp);
// set as a flag that the graphic is being moved, and it's
// parameters should not be adjusted.
gpm = null;
}
/**
* Given a MouseEvent, find a GrabPoint that it is touching, and set the
* moving point to that GrabPoint.
*
* @param e MouseEvent
* @return GrabPoint that is touched by the MouseEvent, null if none are.
*/
public GrabPoint getMovingPoint(MouseEvent e) {
movingPoint = null;
GrabPoint[] gb = getGrabPoints();
Point2D pnt = getProjectionPoint(e);
int x = (int) pnt.getX();
int y = (int) pnt.getY();
for (int i = gb.length - 1; i >= 0; i--) {
if (i != RADIUS_POINT_INDEX && gb[i] != null && gb[i].distance(x, y) == 0) {
setMovingPoint(gb[i]);
// in case the points are on top of each other, the
// last point in the array will take precedence.
break;
}
}
return movingPoint;
}
protected int lastRenderType = -1;
/**
* Check to make sure the grab points are not null. If they are, allocate
* them, and them assign them to the array.
*/
public void assertGrabPoints() {
int rt = getGraphic().getRenderType();
if (rt != lastRenderType) {
clearGrabPoints();
lastRenderType = rt;
}
if (gpr == null) {
gpr = new GrabPoint(-1, -1);
gPoints[RADIUS_POINT_INDEX] = gpr;
}
if (gpnw == null) {
gpnw = new GrabPoint(-1, -1);
gPoints[NW_POINT_INDEX] = gpnw;
}
if (gpn == null) {
gpn = new VerticalGrabPoint(-1, -1);
gPoints[N_POINT_INDEX] = gpn;
}
if (gpne == null) {
gpne = new GrabPoint(-1, -1);
gPoints[NE_POINT_INDEX] = gpne;
}
if (gpw == null) {
gpw = new HorizontalGrabPoint(-1, -1);
gPoints[W_POINT_INDEX] = gpw;
}
if (gpe == null) {
gpe = new HorizontalGrabPoint(-1, -1);
gPoints[E_POINT_INDEX] = gpe;
}
if (gpsw == null) {
gpsw = new GrabPoint(-1, -1);
gPoints[SW_POINT_INDEX] = gpsw;
}
if (gps == null) {
gps = new VerticalGrabPoint(-1, -1);
gPoints[S_POINT_INDEX] = gps;
}
if (gpse == null) {
gpse = new GrabPoint(-1, -1);
gPoints[SE_POINT_INDEX] = gpse;
}
if (gpc == null) {
gpc = new OffsetGrabPoint(-1, -1);
gPoints[CENTER_POINT_INDEX] = gpc;
if (getGraphic().getRenderType() != OMGraphic.RENDERTYPE_LATLON) {
gpc.addGrabPoint(gpnw);
gpc.addGrabPoint(gpn);
gpc.addGrabPoint(gpne);
gpc.addGrabPoint(gpw);
gpc.addGrabPoint(gpe);
gpc.addGrabPoint(gpsw);
gpc.addGrabPoint(gps);
gpc.addGrabPoint(gpse);
}
}
if (gpo == null) {
gpo = new OffsetGrabPoint(-1, -1);
gPoints[OFFSET_POINT_INDEX] = gpo;
gpo.addGrabPoint(gpc);
}
}
protected void clearGrabPoints() {
gpc = null;
gpr = null;
gpnw = null;
gpn = null;
gpne = null;
gpw = null;
gpe = null;
gpsw = null;
gps = null;
gpse = null;
gpo = null;
gPoints[CENTER_POINT_INDEX] = gpc;
gPoints[RADIUS_POINT_INDEX] = gpr;
gPoints[NW_POINT_INDEX] = gpnw;
gPoints[N_POINT_INDEX] = gpn;
gPoints[NE_POINT_INDEX] = gpne;
gPoints[W_POINT_INDEX] = gpw;
gPoints[E_POINT_INDEX] = gpe;
gPoints[SW_POINT_INDEX] = gpsw;
gPoints[S_POINT_INDEX] = gps;
gPoints[SE_POINT_INDEX] = gpse;
gPoints[OFFSET_POINT_INDEX] = gpo;
}
/**
* Set the grab points for the graphic provided, setting them on the extents
* of the graphic. Called when you want to set the grab points off the
* location of the graphic.
*/
public void setGrabPoints(OMGraphic graphic) {
if (!(graphic instanceof OMCircle)) {
return;
}
assertGrabPoints();
OMCircle circle = (OMCircle) graphic;
boolean ntr = circle.getNeedToRegenerate();
int renderType = circle.getRenderType();
int centerx = 0;
int centery = 0;
if (ntr == false) {
if (renderType == OMGraphic.RENDERTYPE_LATLON
|| renderType == OMGraphic.RENDERTYPE_OFFSET) {
if (projection != null) {
LatLonPoint center = circle.getLatLon();
Point2D p = projection.forward(center);
centerx = (int) p.getX();
centery = (int) p.getY();
}
if (renderType == OMGraphic.RENDERTYPE_OFFSET) {
gpo.setX(centerx);
gpo.setY(centery);
centerx += circle.getOffX();
centery += circle.getOffY();
}
} else if (renderType == OMGraphic.RENDERTYPE_XY) {
centerx = circle.getX();
centery = circle.getY();
}
if (renderType == OMGraphic.RENDERTYPE_LATLON) {
Debug.message("eomg", "EditableOMCircle: modifying lat/lon circle");
if (projection != null) {
gpc.set(centerx, centery);
}
// Note that we really don't handle this situation
// well, if there isn't a projection. We kind of
// assume later that there is one, and although there
// shouldn't be massive meltdowns, data could look
// funky on the screen if the projection is
// unavailable.
} else {
// Grab the projected endpoints
Debug.message("eomg", "EditableOMCircle: modifying x/y or offset standard circle");
int height = circle.getHeight() / 2;
int width = circle.getWidth() / 2;
gpc.set(centerx, centery);
gpe.set(centerx + width, centery, true);
gps.set(centerx, centery + height, true);
gpw.set(centerx - width, centery, true);
gpn.set(centerx, centery - height, true);
gpne.set(gpe.getX(), gpn.getY());
gpnw.set(gpw.getX(), gpn.getY());
gpse.set(gpe.getX(), gps.getY());
gpsw.set(gpw.getX(), gps.getY());
gpc.updateOffsets();
// Debug.output("***\nheight:" + height + ", width:" +
// width +
// "\n EditableOMCircle: east at x: " + gpe.getX() +
// ", y:" + gpe.getY());
// Debug.output(" EditableOMCircle: north at x: " +
// gpn.getX() +
// ", y:" + gpn.getY());
// Debug.output(" EditableOMCircle: northeast at x: "
// + gpne.getX() +
// ", y:" + gpne.getY());
// Debug.output(" EditableOMCircle: center at x: " +
// centerx +
// ", y:" + centery);
}
// Check to see if the circle is a offset circle, and set
// the
// offset grab point offsets accordingly.
if (circle.getRenderType() == OMGraphic.RENDERTYPE_OFFSET) {
gpo.updateOffsets();
}
} else {
Debug.message("eomg", "EditableOMCircle.setGrabPoints: graphic needs to be regenerated");
}
}
/**
* Take the current location of the GrabPoints, and modify the location
* parameters of the OMCircle with them. Called when you want the graphic to
* change according to the grab points.
*/
public void setGrabPoints() {
int renderType = circle.getRenderType();
// If the gpm is a movement GP or the gpc, then we need to
// update all the grab points relative location to the gpm,
// and then use the code below.
// If the gpm is the gpo, then the circle moves, too. That's
// OK, because if the gpo is moving, then the other points
// have been moved already.
// If the gpm is one of the other GPs, then we need to update
// the radius or height and width based on that point, as well
// as the other GP locations.
Debug.message("eomg", "EditableOMCircle.setGrabPoints()");
// Do center point for lat/lon or offset circles
if (renderType == OMGraphic.RENDERTYPE_LATLON || renderType == OMGraphic.RENDERTYPE_OFFSET) {
GrabPoint llgp;
// OK, to set the center, if the rendertype is offset,
// then the center lat/lon has to be set to the offset
// point. When the circle is initially defined, this is
// OK because the offset x and y are 0, and the render
// method knows not to render the point yet.
if (// movingPoint == gpo ||
renderType == OMGraphic.RENDERTYPE_OFFSET) {
llgp = gpo;
} else {
// If the moving point is the radius, the the center
// grab point is the lat/lon point.
llgp = gpc;
}
if (projection != null) {
LatLonPoint llp = (LatLonPoint) projection.inverse(llgp.getX(), llgp.getY(), new LatLonPoint.Double());
circle.setCenter(llp);
// Do the radius for LATLON circles.
if (renderType == OMGraphic.RENDERTYPE_LATLON && movingPoint == gpr) {
LatLonPoint llpm = (LatLonPoint) projection.inverse(gpr.getX(), gpr.getY(), new LatLonPoint.Double());
double radius;
if (projection instanceof GeoProj) {
radius = Length.DECIMAL_DEGREE.fromRadians(llpm.distance(llp));
} else {
radius = DrawUtil.distance(llpm.getX(), llpm.getY(), llp.getX(), llp.getY());
}
setRadius(radius);
}
} else {
Debug.message("eomg", "EditableOMCircle.setGrabPoints: projection is null, can't figure out LATLON points for circle.");
}
}
boolean settingOffset = getStateMachine().getState() instanceof GraphicSetOffsetState
&& movingPoint == gpo;
// If the center point is moving, the offset distance changes
if (renderType == OMGraphic.RENDERTYPE_OFFSET && (settingOffset || movingPoint == gpc)) {
// Do the offset point.
circle.setOffX(gpc.getX() - gpo.getX());
circle.setOffY(gpc.getY() - gpo.getY());
Debug.message("eomg", "EditableOMCircle: updating offset distance, ox:"
+ circle.getOffX() + ", oy:" + circle.getOffY());
}
// Do the circle height and width for XY and OFFSET render
// types.
if (renderType == OMGraphic.RENDERTYPE_XY
|| (renderType == OMGraphic.RENDERTYPE_OFFSET && !settingOffset)) {
if (renderType == OMGraphic.RENDERTYPE_XY) {
circle.setX(gpc.getX());
circle.setY(gpc.getY());
}
if (movingPoint instanceof HorizontalGrabPoint) {
// Must be the left or right...
circle.setWidth(Math.abs(movingPoint.getX() - gpc.getX()) * 2);
} else if (movingPoint instanceof VerticalGrabPoint) {
// Must be the top or bottom...
circle.setHeight(Math.abs(movingPoint.getY() - gpc.getY()) * 2);
} else if (movingPoint != gpo && movingPoint != gpc) {
// Must be one of the corners...
circle.setWidth(Math.abs(movingPoint.getX() - gpc.getX()) * 2);
circle.setHeight(Math.abs(movingPoint.getY() - gpc.getY()) * 2);
}
// Debug.output("EditableOMCircle.setGrabPoints():
// movingPoint x:" +
// movingPoint.getX() + ", y:" + movingPoint.getY() +
// ", gpc.x:" + gpc.getX() + ", gpc.y:" + gpc.getY());
}
if (projection != null) {
regenerate(projection);
}
}
/**
* To be overloaded if needed when setting circle's radius.
*
* @param radius in DECIMAL_DEGREES
*/
protected void setRadius(double radius) {
if (circle != null) {
circle.setRadius(radius);
}
}
/**
* Called to set the OffsetGrabPoint to the current mouse location, and
* update the OffsetGrabPoint with all the other GrabPoint locations, so
* everything can shift smoothly. Should also set the OffsetGrabPoint to the
* movingPoint. Should be called only once at the beginning of the general
* movement, in order to set the movingPoint. After that, redraw(e) should
* just be called, and the movingPoint will make the adjustments to the
* graphic that are needed.
*/
public void move(MouseEvent e) {
if (getGraphic().getRenderType() == OMGraphic.RENDERTYPE_LATLON
&& isMouseEventTouchingTheEdge(e)) {
if (gpr == null) {
gpr = new GrabPoint(-1, -1);
}
gpr.set(e.getX(), e.getY());
movingPoint = gpr;
}
}
/**
* Use the current projection to place the graphics on the screen. Has to be
* called to at least assure the graphics that they are ready for rendering.
* Called when the graphic position changes.
*
* @param proj com.bbn.openmap.proj.Projection
* @return true
*/
public boolean generate(Projection proj) {
Debug.message("eomg", "EditableOMCircle.generate()");
if (circle != null)
circle.generate(proj);
for (int i = 0; i < gPoints.length; i++) {
GrabPoint gp = gPoints[i];
if (gp != null) {
gp.generate(proj);
// Why is this here???
// if (gp instanceof OffsetGrabPoint) {
// ((OffsetGrabPoint)gpo).updateOffsets();
// }
}
}
return true;
}
/**
* Given a new projection, the grab points may need to be repositioned off
* the current position of the graphic. Called when the projection changes.
*/
public void regenerate(Projection proj) {
Debug.message("eomg", "EditableOMCircle.regenerate()");
if (circle != null)
circle.regenerate(proj);
setGrabPoints(circle);
generate(proj);
}
/**
* Draw the EditableOMCircle parts into the java.awt.Graphics object. The
* grab points are only rendered if the circle machine state is
* CircleSelectedState.CIRCLE_SELECTED.
*
* @param graphics java.awt.Graphics.
*/
public void render(java.awt.Graphics graphics) {
Debug.message("eomg", "EditableOMCircle.render()");
State state = getStateMachine().getState();
// All the rotation stuff isn't ready for primetime, yet.
// Need to translate the mouse events, too.
// graphics = graphics.create();
// double rotationAngle = OMGraphic.DEFAULT_ROTATIONANGLE;
if (circle == null) {
Debug.message("eomg", "EditableOMCircle.render: null circle.");
return;
}
circle.setVisible(true);
circle.render(graphics);
circle.setVisible(false);
// rotationAngle = circle.getRotationAngle();
int renderType = circle.getRenderType();
// if (rotationAngle != OMGraphic.DEFAULT_ROTATIONANGLE) {
// ((java.awt.Graphics2D)graphics).rotate(rotationAngle,
// circle.getX(), circle.getY());
// }
if (state instanceof GraphicSelectedState || state instanceof GraphicEditState) {
for (int i = 0; i < gPoints.length; i++) {
GrabPoint gp = gPoints[i];
if (gp != null) {
if (i == RADIUS_POINT_INDEX)
continue;
if (renderType == OMGraphic.RENDERTYPE_LATLON && i != CENTER_POINT_INDEX)
continue;
if ((i == OFFSET_POINT_INDEX && renderType == OMGraphic.RENDERTYPE_OFFSET && movingPoint == gpo)
|| (state instanceof GraphicSelectedState && ((i != OFFSET_POINT_INDEX && renderType != OMGraphic.RENDERTYPE_OFFSET) || (renderType == OMGraphic.RENDERTYPE_OFFSET)))) {
gp.setVisible(true);
gp.render(graphics);
gp.setVisible(false);
}
}
}
}
}
/**
* If this EditableOMGraphic has parameters that can be manipulated that are
* independent of other EditableOMGraphic types, then you can provide the
* widgets to control those parameters here. By default, returns the
* GraphicAttributes GUI widgets. If you don't want a GUI to appear when a
* widget is being created/edited, then don't call this method from the
* EditableOMGraphic implementation, and return a null Component from
* getGUI.
*
* @param graphicAttributes the GraphicAttributes to use to get the GUI
* widget from to control those parameters for this EOMG.
* @return java.awt.Component to use to control parameters for this EOMG.
*/
public java.awt.Component getGUI(GraphicAttributes graphicAttributes) {
Debug.message("eomg", "EditableOMCircle.getGUI");
// if (graphicAttributes != null) {
// return graphicAttributes.getColorAndLineGUI();
// } else {
return null;
// }
}
}