// **********************************************************************
//
// <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/EditableOMPoly.java,v $
// $RCSfile: EditableOMPoly.java,v $
// $Revision: 1.19 $
// $Date: 2009/01/21 01:24:41 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.omGraphics;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.net.URL;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JToggleButton;
import javax.swing.SwingConstants;
import com.bbn.openmap.event.UndoEvent;
import com.bbn.openmap.gui.GridBagToolBar;
import com.bbn.openmap.omGraphics.editable.GraphicEditState;
import com.bbn.openmap.omGraphics.editable.GraphicSelectedState;
import com.bbn.openmap.omGraphics.editable.PolyAddNodeState;
import com.bbn.openmap.omGraphics.editable.PolyDeleteNodeState;
import com.bbn.openmap.omGraphics.editable.PolyStateMachine;
import com.bbn.openmap.omGraphics.editable.PolyUndefinedState;
import com.bbn.openmap.proj.GeoProj;
import com.bbn.openmap.proj.ProjMath;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.Debug;
import com.bbn.openmap.util.PaletteHelper;
import com.bbn.openmap.util.stateMachine.State;
/**
* The EditableOMPoly encompasses an OMPoly, providing methods for modifying or
* creating it.
*/
public class EditableOMPoly extends EditableOMAbstractLine {
protected ArrayList<GrabPoint> polyGrabPoints;
protected OffsetGrabPoint gpo; // offset
protected OffsetGrabPoint gpm; // for grabbing the poly and
// moving
// it.
protected OMPoly poly;
/**
* Whether the poly is a polygon, as opposed to a polyline. If the poly
* color is not clear (OMColor.clear) then it will be a polygon. If it is
* clear, then it can be set as a polygon - it's otherwise assumed to be a
* polyline.
*/
protected boolean manualEnclosed = false;
/**
* Set the index of the grab point that should be rendered differently, in
* order to highlight a specific node.
*/
protected int selectNodeIndex = 3;
// We'll have to handle this differently...
public static int OFFSET_POINT_INDEX = -1;
/**
* Create the EditableOMPoly, setting the state machine to create the poly
* off of the gestures.
*/
public EditableOMPoly() {
createGraphic(null);
}
/**
* Create an EditableOMPoly with the polyType and renderType parameters in
* the GraphicAttributes object.
*/
public EditableOMPoly(GraphicAttributes ga) {
createGraphic(ga);
}
/**
* Create the EditableOMPoly with an OMPoly already defined, ready for
* editing.
*
* @param omp OMPoly that should be edited.
*/
public EditableOMPoly(OMPoly omp) {
setGraphic(omp);
}
/**
* 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 EditableOMPoly.
*/
public void init() {
Debug.message("eomg", "EditableOMPoly.init()");
setStateMachine(new PolyStateMachine(this));
gPoints = new GrabPoint[1];
}
/**
* 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 OMPoly) {
poly = (OMPoly) graphic;
poly.setDoShapes(true);
stateMachine.setSelected();
setGrabPoints(poly);
} else {
createGraphic(null);
}
}
/**
* Method checks if the polygon should be enclosed, and then adds an
* addition point to the end of the polygon, setting the end point on top of
* the beginning point. The two points are OffsetGrabPoints that are tied to
* each other's position.
*/
public boolean evaluateEnclosed() {
deletePoint();
boolean enclosed = false;
if (isEnclosed()) {
enclose(true);
enclosed = true;
}
return enclosed;
}
/**
* Method connects the last point to the first point. Make sure they are
* both OffsetGrabPoints. Return true if the points cover the same pixel and
* if they were successfully joined.
*/
protected boolean syncEnclosed() {
try {
OffsetGrabPoint gb0 = (OffsetGrabPoint) polyGrabPoints.get(0);
OffsetGrabPoint ogb = (OffsetGrabPoint) polyGrabPoints.get(polyGrabPoints.size() - 1);
// Check to see if they are over the same point.
if (gb0.getX() == ogb.getX() && gb0.getY() == ogb.getY()) {
// Cross connect them...
gb0.addGrabPoint(ogb);
ogb.addGrabPoint(gb0);
return true;
}
} catch (ClassCastException cce) {
} catch (IndexOutOfBoundsException ioobe) {
}
return false;
}
/**
* Method disconnects the last point from the first point. Make sure they
* are both OffsetGrabPoints.
*/
protected boolean unsyncEnclosed() {
try {
OffsetGrabPoint gb0 = (OffsetGrabPoint) polyGrabPoints.get(0);
OffsetGrabPoint ogb = (OffsetGrabPoint) polyGrabPoints.get(polyGrabPoints.size() - 1);
// disconnect them...
if (gb0.getX() == ogb.getX() && gb0.getY() == ogb.getY()) {
gb0.removeGrabPoint(ogb);
ogb.removeGrabPoint(gb0);
return true;
}
} catch (ClassCastException cce) {
} catch (ArrayIndexOutOfBoundsException aioobe) {
}
return false;
}
public void enclose(boolean e) {
setEnclosed(e);
if (polyGrabPoints == null) {
return;
}
OffsetGrabPoint gb0 = (OffsetGrabPoint) polyGrabPoints.get(0);
OffsetGrabPoint ogb;
if (e) {
// If they should be enclosed...
if (!syncEnclosed()) {
// And they are not already, then add a point, joined
// to the beginning.
ogb = new OffsetGrabPoint(gb0.getX(), gb0.getY());
// Add the new point to end of the poly
addPoint(ogb);
syncEnclosed();
repaint();
} // Else nothing to do...
} else {
// They shouldn't be hooked up, so check to see if they
// are, and disconnect if necessary.
if (unsyncEnclosed()) {
deletePoint(); // Delete attached duplicate point
repaint();
} // else nothing to do.
}
}
/**
* Set the flag to make the polygon enclosed, which automatically connects
* the last point with the first point.
*/
public void setEnclosed(boolean set) {
manualEnclosed = set;
}
/**
* Returns whether the graphic will be a polygon, instead of a polyline.
*/
public boolean isEnclosed() {
return manualEnclosed;
}
/**
* Create and set the graphic within the state machine. The
* GraphicAttributes describe the type of poly to create.
*/
public void createGraphic(GraphicAttributes ga) {
init();
stateMachine.setUndefined();
int renderType = OMGraphic.RENDERTYPE_LATLON;
int lineType = OMGraphic.LINETYPE_GREATCIRCLE;
if (ga != null) {
renderType = ga.getRenderType();
lineType = ga.getLineType();
}
if (Debug.debugging("eomg")) {
Debug.output("EditableOMPoly.createGraphic(): rendertype = " + renderType);
}
if (lineType == OMGraphic.LINETYPE_UNKNOWN) {
lineType = OMGraphic.LINETYPE_GREATCIRCLE;
if (ga != null)
ga.setLineType(OMGraphic.LINETYPE_GREATCIRCLE);
}
this.poly = (OMPoly) createGraphic(renderType, lineType);
if (ga != null) {
ga.setRenderType(poly.getRenderType());
ga.setTo(poly, true);
}
}
/**
* Extendable method to create specific subclasses of OMPolys.
*/
public OMGraphic createGraphic(int renderType, int lineType) {
OMGraphic g = null;
switch (renderType) {
case (OMGraphic.RENDERTYPE_LATLON):
g = new OMPoly(new double[0], OMGraphic.RADIANS, lineType);
break;
case (OMGraphic.RENDERTYPE_OFFSET):
g = new OMPoly(90f, -180f, new int[0], OMPoly.COORDMODE_ORIGIN);
break;
default:
g = new OMPoly(new int[0]);
}
((OMPoly) g).setDoShapes(true);
return g;
}
/**
* Get the OMGraphic being created/modified by the EditableOMPoly.
*/
public OMGraphic getGraphic() {
return poly;
}
/**
* 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);
gpm = null;
}
/**
* Given a MouseEvent, find a GrabPoint that it is touching, and set the
* moving point to that GrabPoint. Called when a MouseEvent happens, and you
* want to find out if a GrabPoint should be used to make modifications to
* the graphic or its position.
*
* @param e MouseEvent
* @return GrabPoint that is touched by the MouseEvent, null if none are.
*/
public GrabPoint getMovingPoint(MouseEvent e) {
GrabPoint gb = super.getMovingPoint(e);
// Since there may be an extra point enclosing the polygon, we
// want to make sure that the start of the polygon is
// returned, instead of the duplicate ending point.
int lastPointIndex = polyGrabPoints.size() - 1;
if (gb != null && gb == (GrabPoint) polyGrabPoints.get(lastPointIndex) && isEnclosed()) {
gb = (GrabPoint) polyGrabPoints.get(0);
setMovingPoint(gb);
}
return gb;
}
/**
* 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() {
// This gets called a lot. Usually, for EditableOMGraphics
// that have the same number of GrabPoints, they can just be
// allocated here. I think we'll have to look at the OMPoly,
// find out how many points have been defined for it (since
// it's variable), and make sure everything's OK.
if (polyGrabPoints == null) {
polyGrabPoints = new ArrayList<GrabPoint>();
}
// At least we know about this one.
if (gpo == null) {
gpo = new OffsetGrabPoint(-1, -1);
}
}
/**
* An internal method that tries to make sure that the grab point for the
* first node, and for the last, in case of an enclosed polygon, are
* OffsetGrabPoints. All of the other points will be regular GrabPoints.
* Usually called when assigning points to a previously defined poly.
*
* @param x the horizontal pixel location of the grab point.
* @param y the vertical pixel location of the grab point.
* @param index the index of the grab point.
* @param last the index of the last point.
*/
protected GrabPoint createGrabPoint(int x, int y, int index, int last) {
if (index == 0 || (index == last && (isEnclosed()))) {
return new OffsetGrabPoint(x, y);
} else {
return new GrabPoint(x, y);
}
}
/**
* 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
* points of the graphic.
*/
public void setGrabPoints(OMGraphic graphic) {
if (!(graphic instanceof OMPoly)) {
return;
}
assertGrabPoints();
polyGrabPoints.clear();
arrayCleared = true;
gpo.clear();
OMPoly poly = (OMPoly) graphic;
boolean ntr = poly.getNeedToRegenerate();
int renderType = poly.getRenderType();
Point2D p = new Point2D.Double();
Projection proj = getProjection();
GrabPoint gb;
int i;
int npts;
boolean geoProj = proj instanceof GeoProj;
if (ntr == false) {
if (renderType == OMGraphic.RENDERTYPE_LATLON) {
Debug.message("eomg", "EditableOMPoly: modifying lat/lon line");
if (proj != null) {
double[] ll = poly.getLatLonArray();
boolean rads = poly.getUnits() == OMGraphic.RADIANS;
gb = null; // reset for this loop
for (i = 0; i < ll.length - 1; i += 2) {
if (geoProj) {
((GeoProj) proj).forward(ll[i], ll[i + 1], p, rads);
} else {
proj.forward(ll[i], ll[i + 1], p);
}
// Need to add a grab point for this
// coordinate
gb = new OffsetGrabPoint((int) p.getX(), (int) p.getY());
polyGrabPoints.add(gb);
}
}
} else if (renderType == OMGraphic.RENDERTYPE_OFFSET) {
// Grab the projected endpoints
Debug.message("eomg", "EditableOMPoly: modifying offset poly");
int x;
int y;
npts = poly.xs.length;
// Check to see if the poly is a offset poly, and set
// the
// offset grab point accordingly.
if (proj != null) {
if (geoProj) {
((GeoProj) proj).forward(poly.lat, poly.lon, p, true);
} else {
proj.forward(poly.lat, poly.lon, p);
}
gpo.set((int) p.getX(), (int) p.getY());
if (poly.coordMode == OMPoly.COORDMODE_ORIGIN) {
for (i = 0; i < npts; i++) {
x = (int) (poly.xs[i] + p.getX());
y = (int) (poly.ys[i] + p.getY());
gb = new OffsetGrabPoint(x, y);
polyGrabPoints.add(gb);
}
} else { // CMode Previous offset deltas
int lastX = (int) p.getX();
int lastY = (int) p.getY();
for (i = 0; i < npts; i++) {
x = poly.xs[i] + lastX;
y = poly.ys[i] + lastY;
gb = new OffsetGrabPoint(x, y);
polyGrabPoints.add(gb);
lastX += x;
lastY += y;
}
}
}
} else {
npts = poly.xs.length;
Debug.message("eomg", "EditableOMPoly: modifying x/y poly");
for (i = 0; i < npts; i++) {
gb = new OffsetGrabPoint(poly.xs[i], poly.ys[i]);
polyGrabPoints.add(gb);
}
}
// Add the || to maintain manualEnclosed if it was
// externally set before the OMPoly is actually defined,
// indicating that the user wants to draw a polygon.
setEnclosed(syncEnclosed() || isEnclosed());
addPolyGrabPointsToOGP(gpo);
} else {
Debug.message("eomg", "EditableOMPoly.setGrabPoints: graphic needs to be regenerated ");
}
}
/**
* Take the current location of the GrabPoints, and modify the location
* parameters of the OMPoly with them. Called when you want the graphic to
* change according to the grab points.
*/
public void setGrabPoints() {
int renderType = poly.getRenderType();
Projection proj = getProjection();
if (renderType == OMGraphic.RENDERTYPE_LATLON) {
if (proj != null) {
double[] newCoords = new double[polyGrabPoints.size() * 2];
// OK, this code resets the location of every point slightly to
// the inverse location of the grab points. So if you grab one
// node and move it, all of the precise values of each node
// actually changes. As we go through the array of grab points,
// we can check the corresponding projected location of the
// current node and it matches the grab point, just use the
// current poly value.
double[] currentCoords = ProjMath.arrayRadToDeg(poly.getLatLonArrayCopy());
Point2D polyPoint = new Point2D.Double();
LatLonPoint movedPoint = new LatLonPoint.Double();
for (int i = 0; i < polyGrabPoints.size(); i++) {
GrabPoint gb = (GrabPoint) polyGrabPoints.get(i);
int latIndex = i * 2;
int lonIndex = i * 2 + 1;
// Set the current coordinate to the GP coordinate - the
// logic is easier this way
proj.inverse(gb.getX(), gb.getY(), movedPoint);
newCoords[latIndex] = movedPoint.getY();
newCoords[lonIndex] = movedPoint.getX();
if (lonIndex < currentCoords.length && gpm != gb) {
double lat = currentCoords[latIndex];
double lon = currentCoords[lonIndex];
polyPoint = proj.forward(lat, lon, polyPoint);
// if the grab point, forward projected, is in the same
// pixel as the old point, then use the previous value.
boolean pointUnmoved = polyPoint.getX() == gb.getX()
&& polyPoint.getY() == gb.getY();
if (pointUnmoved) {
newCoords[latIndex] = currentCoords[latIndex];
newCoords[lonIndex] = currentCoords[lonIndex];
}
}
}
double[] oldCoords = ProjMath.arrayRadToDeg(poly.getLatLonArrayCopy());
int changeCount = 0;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < oldCoords.length - 1; i += 2) {
if (oldCoords[i] != newCoords[i] || oldCoords[i + 1] != newCoords[i + 1]) {
sb.append(i / 2).append(" ");
changeCount++;
}
}
/*
* Debug.output("EOMP.SGP: " + changeCount +
* " points changed out of " + (oldCoords.length / 2) + ", " +
* sb.toString());
*/
poly.setLocation(ProjMath.arrayDegToRad(newCoords), OMGraphic.RADIANS);
} else {
Debug.message("eomg", "EditableOMPoly.setGrabPoints: projection is null, can't figure out LATLON points for poly.");
}
}
if (renderType == OMGraphic.RENDERTYPE_XY || renderType == OMGraphic.RENDERTYPE_OFFSET) {
int[] ints = new int[polyGrabPoints.size() * 2];
if (renderType == OMGraphic.RENDERTYPE_OFFSET && gpo != null) {
// If offset rendertype, the x-y have to be offset
// distances, not screen pixel locations. For the
// polygon, you also need to take into account that
// the ints can represent 2 different things: distance
// from the origin (Offset) or distance from the
// previous point. Need to check with the poly to
// find out which to do.
GrabPoint previous = gpo;
for (int i = 0; i < polyGrabPoints.size(); i++) {
GrabPoint gb = (GrabPoint) polyGrabPoints.get(i);
if (poly.coordMode == OMPoly.COORDMODE_PREVIOUS) {
ints[2 * i] = gb.getX() - previous.getX();
ints[2 * i + 1] = gb.getY() - previous.getY();
previous = gb;
} else {
ints[2 * i] = gb.getX() - gpo.getX();
ints[2 * i + 1] = gb.getY() - gpo.getY();
}
}
if (proj != null) {
LatLonPoint llp = proj.inverse(gpo.getX(), gpo.getY(), new LatLonPoint.Double());
poly.setLocation(llp.getRadLat(), llp.getRadLon(), OMGraphic.RADIANS, ints);
} else {
Debug.message("eomg", "EditableOMPoly.setGrabPoints: projection is null, can't figure out LATLON points for poly offset.");
}
} else {
for (int i = 0; i < polyGrabPoints.size(); i++) {
GrabPoint gb = (GrabPoint) polyGrabPoints.get(i);
ints[2 * i] = gb.getX();
ints[2 * i + 1] = gb.getY();
}
poly.setLocation(ints);
}
}
}
/**
* Add a point to the end of the polyline/polygon and then make it the
* moving one.
*
* @return the index for the point in the polygon, starting with 0.
*/
public int addMovingPoint(int x, int y) {
int position = addPoint(x, y);
setMovingPoint((GrabPoint) polyGrabPoints.get(position));
return position;
}
/**
* Add a point to the end of the polyline/polygon.
*
* @return the index for the point in the polygon, starting with 0.
*/
public int addPoint(int x, int y) {
return addPoint(x, y, Integer.MAX_VALUE);
}
/**
* Add a point at a certain point in the polygon coordinate list. If the
* position is less than zero, the point will be the starting point. If the
* position is greater than the list of current points, the point will be
* added to the end of the poly.
*
* @return the index for the point in the polygon, starting with 0.
*/
public int addPoint(int x, int y, int position) {
return addPoint(new OffsetGrabPoint(x, y), position);
}
/**
* Add a point at a certain point in the polygon coordinate list. If the
* position is less than zero, the point will be the starting point. If the
* position is greater than the list of current points, the point will be
* added to the end of the poly. This method is convenient because it lets
* you define the GrabPoint object to use for the node, in case you need a
* special type of GrabPoint.
*
* @param gp the GrabPoint set to the screen coordinates of the point to be
* added.
* @return the index for the point in the polygon, starting with 0.
*/
public int addPoint(GrabPoint gp) {
return addPoint(gp, Integer.MAX_VALUE);
}
/**
* Add a point at a certain point in the polygon coordinate list. If the
* position is less than zero, the point will be the starting point. If the
* position is greater than the list of current points, the point will be
* added to the end of the poly. This method is convenient because it lets
* you define the GrabPoint object to use for the node, in case you need a
* special type of GrabPoint.
*
* @return the index for the point in the polygon, starting with 0.
*/
public int addPoint(GrabPoint gp, int position) {
if (gp == null) {
return -1;
}
int x = gp.getX();
int y = gp.getY();
int renderType = poly.getRenderType();
Projection proj = getProjection();
if (renderType == OMGraphic.RENDERTYPE_LATLON) {
Debug.message("eomg", "EditableOMPoly: adding point to lat/lon poly");
if (proj != null) {
double[] ll = poly.getLatLonArray();
int actualPosition = (position == Integer.MAX_VALUE ? ll.length : position * 2);
LatLonPoint llpnt = proj.inverse(x, y, new LatLonPoint.Double());
if (Debug.debugging("eomp")) {
Debug.output("EditableOMPoly: adding point to lat/lon poly at " + x + ", " + y
+ ": " + llpnt + ", at the end of ");
for (int j = 0; j < ll.length; j += 2) {
Debug.output(ll[j] + ", " + ll[j + 1]);
}
}
double[] newll = new double[ll.length + 2];
double newlat = llpnt.getRadLat();
double newlon = llpnt.getRadLon();
if (actualPosition >= ll.length) {
// Put the new points at the end
if (ll.length != 0) {
System.arraycopy(ll, 0, newll, 0, ll.length);
}
newll[ll.length] = newlat;
newll[ll.length + 1] = newlon;
position = ll.length / 2;
} else if (actualPosition <= 0) {
// Put the new point at the beginning
System.arraycopy(ll, 0, newll, 2, ll.length);
newll[0] = newlat;
newll[1] = newlon;
position = 0;
} else {
// actualPosition because there are 2 floats for every
// position.
newll[actualPosition] = newlat;
newll[actualPosition + 1] = newlon;
System.arraycopy(ll, 0, newll, 0, actualPosition);
System.arraycopy(ll, actualPosition, newll, actualPosition + 2, ll.length
- actualPosition);
}
poly.setLocation(newll, OMGraphic.RADIANS);
}
} else if (renderType == OMGraphic.RENDERTYPE_XY) {
// Grab the projected endpoints
Debug.message("eomg", "EditableOMPoly: adding point to x/y poly");
int currentLength = poly.xs.length;
int[] newxs = new int[currentLength + 1];
int[] newys = new int[currentLength + 1];
if (position >= currentLength) {
// Put the new points at the end
System.arraycopy(poly.xs, 0, newxs, 0, currentLength);
System.arraycopy(poly.ys, 0, newys, 0, currentLength);
newxs[currentLength] = x;
newys[currentLength] = y;
position = currentLength;
} else if (position <= 0) {
// Put the new points at the beginning
System.arraycopy(poly.xs, 0, newxs, 1, currentLength);
System.arraycopy(poly.ys, 0, newys, 1, currentLength);
newxs[0] = x;
newys[0] = y;
position = 0;
} else {
newxs[position] = x;
newys[position] = y;
System.arraycopy(poly.xs, 0, newxs, 0, position);
System.arraycopy(poly.xs, position, newxs, position + 1, currentLength - position);
System.arraycopy(poly.ys, 0, newys, 0, position);
System.arraycopy(poly.ys, position, newys, position + 1, currentLength - position);
}
poly.setLocation(newxs, newys);
} else {
// Rendertype is offset...
// Grab the projected endpoints
Debug.message("eomg", "EditableOMPoly: adding point to offset poly");
int currentLength = poly.xs.length;
int[] newxs = new int[currentLength + 1];
int[] newys = new int[currentLength + 1];
if (position >= currentLength) {
// Put the new points at the end
position = currentLength;
System.arraycopy(poly.xs, 0, newxs, 0, currentLength);
System.arraycopy(poly.ys, 0, newys, 0, currentLength);
} else if (position <= 0) {
// Put the new points at the beginning
position = 0;
System.arraycopy(poly.xs, 0, newxs, 1, currentLength);
System.arraycopy(poly.ys, 0, newys, 1, currentLength);
} else {
System.arraycopy(poly.xs, 0, newxs, 0, position);
System.arraycopy(poly.xs, position, newxs, position + 1, currentLength - position);
System.arraycopy(poly.ys, 0, newys, 0, position);
System.arraycopy(poly.ys, position, newys, position + 1, currentLength - position);
}
int offsetX;
int offsetY;
if (gpo.getX() == -1 && gpo.getY() == -1) {
offsetX = proj.getWidth() / 2;
offsetY = proj.getHeight() / 2;
} else {
offsetX = gpo.getX();
offsetY = gpo.getY();
}
if (poly.coordMode == OMPoly.COORDMODE_ORIGIN || position == 0) { // cover
// the first point
newxs[position] = x - offsetX;
newys[position] = y - offsetY;
} else { // CMode Previous offset deltas
newxs[position] = x - offsetX - newxs[position - 1];
newys[position] = y - offsetY - newys[position - 1];
}
if (position == 0) {
// Could call projection.getCenter() but that might
// break if/when we make other projection
// libraries/paradigms active.
LatLonPoint llpnt = proj.inverse(offsetX, offsetY, new LatLonPoint.Double());
poly.lat = llpnt.getRadLat();
poly.lon = llpnt.getRadLon();
}
poly.setLocation(poly.lat, poly.lon, OMGraphic.RADIANS, newxs, newys);
}
// Need to reset the arrowhead when an end point is added,
// removing it from the shape when the point gets added.
// Otherwise, you end up with a arrowhead at each junction of
// the polygon.
OMArrowHead omah = poly.getArrowHead();
poly.setArrowHead(null);
// Reset the arrowhead so it will get drawn on the new
// segment.
poly.setArrowHead(omah);
polyGrabPoints.add(position, gp);
if (gpo != null) {
gpo.addGrabPoint(gp);
}
// This is the standard call that needs to be made here, the
// arrowhead changes are around this.
poly.regenerate(proj);
gp.generate(proj);
return position;
}
/**
* Delete a point off the end of the polyline/polygon.
*/
public void deletePoint() {
deletePoint(Integer.MAX_VALUE);
}
/**
* Delete a point at a certain point in the polygon coordinate list. If the
* position is less than zero, the deleted point will be the starting point.
* If the position is greater than the list of current points, the point
* will be deleted from the end of the poly.
*/
public void deletePoint(int position) {
int renderType = poly.getRenderType();
Projection proj = getProjection();
boolean needToHookUp = false;
if (position <= 0 && isEnclosed()) {
// if the position is 0 and the polygon is enclosed, we
// need to disengage the two points, then reattach.
enclose(false);
needToHookUp = true;
}
if (renderType == OMGraphic.RENDERTYPE_LATLON) {
Debug.message("eomg", "EditableOMPoly: removing point from lat/lon poly");
if (proj != null) {
double[] ll = poly.getLatLonArray();
double[] newll = new double[ll.length - 2];
int actualPosition = (position == Integer.MAX_VALUE ? ll.length : position * 2);
if (actualPosition >= ll.length) {
// Pull the new points off the end
System.arraycopy(ll, 0, newll, 0, ll.length - 2);
position = (ll.length - 2) / 2;
} else if (actualPosition <= 0) {
// Pull the new points off the beginning
System.arraycopy(ll, 2, newll, 0, ll.length - 2);
position = 0;
} else {
// actualPosition because there are 2 floats for
// every
// position.
System.arraycopy(ll, 0, newll, 0, actualPosition);
System.arraycopy(ll, actualPosition + 2, newll, actualPosition, ll.length
- actualPosition - 2);
}
poly.setLocation(newll, poly.getUnits());
}
} else {
// Grab the projected endpoints
Debug.message("eomg", "EditableOMPoly: removing point from x/y or offset poly");
int currentLength = poly.xs.length;
int[] newxs = new int[currentLength - 1];
int[] newys = new int[currentLength - 1];
if (position >= currentLength) {
// Pull the points from the end...
System.arraycopy(poly.xs, 0, newxs, 0, currentLength - 1);
System.arraycopy(poly.ys, 0, newys, 0, currentLength - 1);
position = currentLength - 1;
} else if (position <= 0) {
// Pull the points from the beginning...
System.arraycopy(poly.xs, 1, newxs, 0, currentLength - 1);
System.arraycopy(poly.ys, 1, newys, 0, currentLength - 1);
position = 0;
} else {
System.arraycopy(poly.xs, 0, newxs, 0, position);
System.arraycopy(poly.xs, position + 1, newxs, position, currentLength - position
- 1);
System.arraycopy(poly.ys, 0, newys, 0, position);
System.arraycopy(poly.ys, position + 1, newys, position, currentLength - position
- 1);
}
if (poly.getRenderType() == OMGraphic.RENDERTYPE_OFFSET) {
poly.setLocation(poly.lat, poly.lon, poly.getUnits(), newxs, newys);
} else {
poly.setLocation(newxs, newys);
}
}
if (proj != null) {
poly.regenerate(proj);
}
// Remove the GrabPoint for the deleted spot.
GrabPoint gp = (GrabPoint) polyGrabPoints.remove(position);
if (gpo != null && gp != null) {
gpo.removeGrabPoint(gp);
}
if (needToHookUp) {
enclose(true);
}
}
/**
* 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) {
// Need to check to see if the OffsetGrabPoint is currently
// being used. If not, just use it, otherwise, will need to
// create a special one for the move.
Point2D pnt = getProjectionPoint(e);
int x = (int) pnt.getX();
int y = (int) pnt.getY();
if (poly.getRenderType() == OMGraphic.RENDERTYPE_OFFSET) {
gpm = new OffsetGrabPoint(x, y);
gpm.clear();
} else {
gpm = gpo;
gpm.clear();
gpm.set(x, y);
}
// Move all the other points along with the offset point...
addPolyGrabPointsToOGP(gpm);
movingPoint = gpm;
}
/**
* This method adds all the GrabPoints associated with the polygon nodes and
* adds them to the offset GrabPoint representing the lat/lon anchor point.
*/
protected void addPolyGrabPointsToOGP(OffsetGrabPoint ogp) {
if (ogp == null)
return;
// Reset the points to the offset point.
int count = 0;
for (GrabPoint gb : polyGrabPoints) {
if (gb != null) {
ogp.addGrabPoint(gb);
count++;
}
}
ogp.updateOffsets();
}
/**
* 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", "EditableOMPoly.generate()");
if (poly != null) {
poly.generate(proj);
}
generateGrabPoints(proj);
return true;
}
/**
* Generate the grab points, checking the OMGraphic to see if it contains
* information about what the grab points should look like.
*
* @param proj
*/
protected void generateGrabPoints(Projection proj) {
DrawingAttributes grabPointDA = null;
Object obj = poly.getAttribute(EditableOMGraphic.GRAB_POINT_DRAWING_ATTRIBUTES_ATTRIBUTE);
if (obj instanceof DrawingAttributes) {
grabPointDA = (DrawingAttributes) obj;
}
int index = 0;
// Generate all the grab points, but also check to make sure the drawing
// attributes are right
for (GrabPoint gb : polyGrabPoints) {
if (gb != null) {
if (selectNodeIndex == index) {
Object daobj = poly.getAttribute(EditableOMGraphic.SELECTED_GRAB_POINT_DRAWING_ATTRIBUTES_ATTRIBUTE);
if (daobj instanceof DrawingAttributes) {
((DrawingAttributes) daobj).setTo(gb);
}
} else if (grabPointDA != null) {
grabPointDA.setTo(gb);
} else {
gb.setDefaultDrawingAttributes(GrabPoint.DEFAULT_RADIUS);
}
gb.generate(proj);
}
index++;
}
if (gpo != null) {
if (grabPointDA != null) {
grabPointDA.setTo(gpo);
} else {
gpo.setDefaultDrawingAttributes(GrabPoint.DEFAULT_RADIUS);
}
gpo.generate(proj);
gpo.updateOffsets();
}
}
/**
* 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", "EditableOMPoly.regenerate()");
if (poly != null) {
poly.generate(proj);
setGrabPoints(poly);
}
generateGrabPoints(proj);
}
/**
* Draw the EditableOMPoly parts into the java.awt.Graphics object. The grab
* points are only rendered if the poly machine state is
* PolySelectedState.POLY_SELECTED.
*
* @param graphics java.awt.Graphics.
*/
public void render(java.awt.Graphics graphics) {
Debug.message("eomg", "EditableOMPoly.render()");
State state = getStateMachine().getState();
if (poly != null && !(state instanceof PolyUndefinedState)) {
poly.setVisible(true);
poly.render(graphics);
poly.setVisible(false);
} else {
Debug.message("eomg", "EditableOMPoly.render: null or undefined poly.");
return;
}
// Render the points actually on the polygon
if (state instanceof GraphicSelectedState || state instanceof PolyAddNodeState
|| state instanceof PolyDeleteNodeState) {
for (GrabPoint gb : polyGrabPoints) {
if (gb != null) {
gb.setVisible(true);
gb.render(graphics);
gb.setVisible(false);
}
}
}
// In certain conditions, render the offset grab point.
if (state instanceof GraphicSelectedState || state instanceof GraphicEditState /*
* ||
* state
* instanceof
* PolySetOffsetState
*/) {
if (gpo != null && poly.getRenderType() == OMGraphic.RENDERTYPE_OFFSET) {
gpo.setVisible(true);
gpo.render(graphics);
gpo.setVisible(false);
}
}
}
// ///////////// Special Grab Point functions
// /////////////////////
// Since the GrabPoints only refer to the points actually on the
// polygon, we have to make sure that the generic
// EditableOMGraphic grab point methods handle that. The
// OffsetGrabPointIndex is -1, so we have to look out for that and
// use the gpo when appropriate.
// /////////////////////////////////////////////////////////////////
/**
* Set the grab point objects within the EditableOMGraphic array. For the
* EditableOMPoly, with its variable number of GrabPoints, this just sets up
* a new list of all the grab points to look at. It's different than the
* polyGrabPoints, which are the grab points just on the polygon. This list
* includes the offset grab point. This method should be called when a new
* point gets added to the polygon, and should take an array of all the
* GrabPoints created. It will add the offsetGrabPoint to the end of the
* array.
*
* @param points a GrabPoint[] for the points on the polygon.
* @return true if the grab point array was exactly what the
* EditableOMGraphic was expecting, in terms of length of the
* GrabPoint array length. The method copies the array values that
* fit into the resident array.
*/
public boolean setGrabPoints(GrabPoint[] points) {
gPoints = new GrabPoint[points.length + 1];
System.arraycopy(gPoints, 0, points, 0, points.length);
gPoints[points.length] = gpo;
return true;
}
/**
* Flag to keep track of when the grab point array has been rebuilt in
* setGrabPoints().
*/
boolean arrayCleared = true;
/**
* Get the array of grab points used for the EditableOMGraphic. Creates the
* array by copying all the grab points out of the ArrayList, and tacking
* the offset grab point to the end.
*/
public GrabPoint[] getGrabPoints() {
int size = polyGrabPoints.size();
// The second half of the test is the fix to the bug that caused
// OMEditablePolys to be unresponsive when the colors changed. Thanks,
// Stephane!
// if (gPoints.length != size + 1
// || ((size > 0) && (!gPoints[0].equals(polyGrabPoints.get(0))))) {
if (gPoints.length != size + 1 || arrayCleared) {
arrayCleared = false;
Debug.message("eomg", "EditableOMPoly.getGrabPoints(): recreating grab points");
gPoints = new GrabPoint[size + 1];
int counter = 0;
for (GrabPoint gb : polyGrabPoints) {
gPoints[counter++] = gb;
}
gPoints[counter] = gpo;
}
return gPoints;
}
/**
* Set the GrabPoint at a particule index of the array. This can be used to
* tie two different grab points together. This used to work with the
* gPoints array declared in EditableOMGraphic - no longer. If the index is
* -1, the offset grab point is set, and any other index refers to the
* concurrent polygon point.
*
* @param gb GrabPoint to assign within array.
* @param index the index of the array to put the GrabPoint. This index
* should be -1 for the offset grab point, or the index of the corner
* of the poly, in order starting from 0.
* @return If the grab point or array is null, or if the index is outside
* the range of the array, false is returned. If everything goes OK,
* then true is returned.
*/
public boolean setGrabPoint(GrabPoint gb, int index) {
// We might have to take care of the offset grab point
// connections here...
if (index == OFFSET_POINT_INDEX) {
gpo = (OffsetGrabPoint) gb;
return true;
} else {
return super.setGrabPoint(gb, index);
}
}
/**
* Given a grab point, return its index into the polygon array. If its not
* in the array, the next available index is returned.
*/
public int whichGrabPoint(GrabPoint gp) {
GrabPoint[] points = getGrabPoints();
for (int i = 0; i < points.length; i++) {
if (gp == points[i]) {
if (gp == gpo) {
return OFFSET_POINT_INDEX;
} else {
return i;
}
}
}
return points.length;
}
/**
* Return a particular GrabPoint at a particular point in the array. The
* EditableOMGraphic should describe which indexes refer to which grab
* points in the EOMG GrabPoint array. If the index is OFFSET_POINT_INDEX,
* the offset point is returned. If the index is otherwise outside the range
* of the array, null is returned.
*/
public GrabPoint getGrabPoint(int index) {
if (index == OFFSET_POINT_INDEX) {
return gpo;
} else {
return super.getGrabPoint(index);
}
}
/**
* Adds widgets to modify polygon.
*
* @param graphicAttributes the GraphicAttributes to use to get the GUI
* widget from to control those parameters for this EOMG.
* @return Component to use to control parameters for this EOMG.
*/
public Component getGUI(GraphicAttributes graphicAttributes) {
Debug.message("eomg", "EditableOMPoly.getGUI");
if (graphicAttributes != null) {
JMenu ahm = getArrowHeadMenu();
graphicAttributes.setLineMenuAdditions(new JMenu[] { ahm });
// JComponent gaGUI = (JComponent) graphicAttributes.getGUI();
JComponent toolbar = createAttributePanel(graphicAttributes);
getPolyGUI(graphicAttributes.getOrientation(), toolbar);
return toolbar;
} else {
return getPolyGUI();
}
}
JToggleButton polygonButton = null;
JButton extButton = null;
JButton addButton = null;
JButton deleteButton = null;
public void enablePolygonButton(boolean enable) {
if (polygonButton != null) {
polygonButton.setEnabled(enable);
}
}
public void enablePolygonEditButtons(boolean enable) {
if (extButton != null) {
extButton.setEnabled(enable);
}
if (addButton != null) {
addButton.setEnabled(enable);
}
if (deleteButton != null) {
deleteButton.setEnabled(enable);
}
}
public JComponent getPolyGUI() {
return getPolyGUI(true, true, true, true, SwingConstants.HORIZONTAL);
}
public JComponent getPolyGUI(int orientation, JComponent toolbar) {
return getPolyGUI(true, true, true, true, orientation, toolbar);
}
public JComponent getPolyGUI(boolean includeEnclose, boolean includeExt, boolean includeAdd,
boolean includeDelete, int orientation) {
return getPolyGUI(includeEnclose, includeExt, includeAdd, includeDelete, orientation, null);
}
public JComponent getPolyGUI(boolean includeEnclose, boolean includeExt, boolean includeAdd,
boolean includeDelete, int orientation, JComponent buttonBox) {
if (buttonBox == null) {
buttonBox = new GridBagToolBar();
((GridBagToolBar) buttonBox).setOrientation(orientation);
}
buttonBox.add(PaletteHelper.getToolBarFill(orientation));
URL url;
ImageIcon imageIcon;
if (polygonButton == null) {
url = getImageURL("enclosepoly.gif");
imageIcon = new ImageIcon(url);
polygonButton = new JToggleButton(imageIcon);
polygonButton.setToolTipText(i18n.get(EditableOMPoly.class, "polygonButton.tooltip", "Automatically link first and last nodes"));
polygonButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (getStateMachine().getState() instanceof GraphicSelectedState) {
enclose(((JToggleButton) e.getSource()).isSelected());
} else {
setEnclosed(((JToggleButton) e.getSource()).isSelected());
}
updateCurrentState(null);
}
});
}
polygonButton.setSelected(isEnclosed());
if (includeEnclose) {
buttonBox.add(polygonButton);
}
if (extButton == null) {
url = getImageURL("addpoint.gif");
imageIcon = new ImageIcon(url);
extButton = new JButton(imageIcon);
extButton.setToolTipText(i18n.get(EditableOMPoly.class, "extButton.tooltip", "Add a point to the polygon"));
extButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// If an enclosed poly is having nodes added to it, we need
// to
// remove the connection, but make a note to reconnect after
// editing.
if (isEnclosed()) {
enclose(false);
setEnclosed(true);
}
((PolyStateMachine) stateMachine).setAddPoint();
enablePolygonEditButtons(false);
}
});
}
extButton.setEnabled(false);
if (includeExt) {
buttonBox.add(extButton);
}
if (addButton == null) {
url = getImageURL("addnode.gif");
imageIcon = new ImageIcon(url);
addButton = new JButton(imageIcon);
addButton.setToolTipText(i18n.get(EditableOMPoly.class, "addButton.tooltip", "Add a node to the polygon"));
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
((PolyStateMachine) stateMachine).setAddNode();
enablePolygonEditButtons(false);
}
});
}
addButton.setEnabled(false);
if (includeAdd) {
buttonBox.add(addButton);
}
if (deleteButton == null) {
url = getImageURL("deletepoint.gif");
imageIcon = new ImageIcon(url);
deleteButton = new JButton(imageIcon);
deleteButton.setToolTipText(i18n.get(EditableOMPoly.class, "deleteButton.tooltip", "Delete a node from the polygon"));
deleteButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
((PolyStateMachine) stateMachine).setDeleteNode();
enablePolygonEditButtons(false);
}
});
}
deleteButton.setEnabled(false);
if (includeDelete) {
buttonBox.add(deleteButton);
}
return buttonBox;
}
public java.net.URL getImageURL(String imageName) {
return EditableOMPoly.class.getResource(imageName);
}
/**
* @return the selectNodeIndex
*/
public int getSelectNodeIndex() {
return selectNodeIndex;
}
/**
* @param selectNodeIndex the selectNodeIndex to set
*/
public void setSelectNodeIndex(int selectNodeIndex) {
this.selectNodeIndex = selectNodeIndex;
}
/**
* Make sure no node is highlighted.
*/
public void clearSelectedNode() {
this.selectNodeIndex = -1;
}
/**
* Create an UndoEvent that can get an OMPoly back to what it looks like
* right now.
*/
protected UndoEvent createUndoEventForCurrentState(String whatHappened) {
if (whatHappened == null) {
whatHappened = i18n.get(this.getClass(), "polygonUndoString", "Edit");
}
return new OMPolyUndoEvent(this, whatHappened);
}
/**
* Subclass for undoing edits for OMPoly classes, handles enclose/unenclose
* events.
*
* @author ddietrick
*/
public static class OMPolyUndoEvent extends OMGraphicUndoEvent implements UndoEvent {
protected boolean enclosed = false;
public OMPolyUndoEvent(EditableOMPoly eomp, String description) {
super(eomp, description);
enclosed = eomp.manualEnclosed;
}
protected void setSubclassState() {
((EditableOMPoly) eomg).polygonButton.setSelected(enclosed);
}
}
}