001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * JUMP is Copyright (C) 2003 Vivid Solutions
006: *
007: * This program implements extensions to JUMP and is
008: * Copyright (C) 2004 Integrated Systems Analysts, Inc.
009: *
010: * This program is free software; you can redistribute it and/or
011: * modify it under the terms of the GNU General Public License
012: * as published by the Free Software Foundation; either version 2
013: * of the License, or (at your option) any later version.
014: *
015: * This program is distributed in the hope that it will be useful,
016: * but WITHOUT ANY WARRANTY; without even the implied warranty of
017: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
018: * GNU General Public License for more details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with this program; if not, write to the Free Software
022: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
023: *
024: * For more information, contact:
025: *
026: * Integrated Systems Analysts, Inc.
027: * 630C Anchors St., Suite 101
028: * Fort Walton Beach, Florida
029: * USA
030: *
031: * (850)862-7321
032: */
033:
034: package org.openjump.core.ui.plugin.edittoolbox.cursortools;
035:
036: import java.awt.Shape;
037: import java.awt.event.KeyEvent;
038: import java.awt.event.KeyListener;
039: import java.awt.event.MouseEvent;
040: import java.awt.geom.GeneralPath;
041: import java.awt.geom.NoninvertibleTransformException;
042: import java.awt.geom.Point2D;
043: import java.text.DecimalFormat;
044: import java.util.ArrayList;
045: import java.util.Collections;
046: import java.util.List;
047:
048: import org.openjump.core.ui.plugin.edittoolbox.tab.ConstraintManager;
049:
050: import com.vividsolutions.jts.geom.Coordinate;
051: import com.vividsolutions.jts.util.Assert;
052: import com.vividsolutions.jump.I18N;
053: import com.vividsolutions.jump.workbench.ui.LayerViewPanel;
054: import com.vividsolutions.jump.workbench.ui.WorkbenchFrame;
055: import com.vividsolutions.jump.workbench.ui.cursortool.AbstractCursorTool;
056: import com.vividsolutions.jump.workbench.ui.cursortool.MultiClickTool;
057:
058: /**
059: * A VisualIndicatorTool that allows the user to draw shapes with multiple
060: * vertices. Double-clicking ends the gesture.
061: */
062: public abstract class ConstrainedMultiClickTool extends
063: AbstractCursorTool {
064:
065: //24.iii.03 Dropped drag handling because it's too easy to do a micro-drag when
066: //we mean a click. [Jon Aquino]
067: final static String lengthST = I18N
068: .get("org.openjump.core.ui.plugin.edittoolbox.cursortools.length");
069: final static String angleST = I18N
070: .get("org.openjump.core.ui.plugin.edittoolbox.cursortools.angle");
071: final static String degrees = I18N
072: .get("org.openjump.core.ui.plugin.edittoolbox.cursortools.degrees");
073:
074: protected List coordinates = new ArrayList();
075: protected Coordinate tentativeCoordinate;
076: protected boolean drawClosed = true;
077: private ConstraintManager constraintManager;
078: private LayerViewPanel panel;
079: private WorkbenchFrame frame;
080:
081: public ConstrainedMultiClickTool() {
082: }
083:
084: public boolean isRightMouseButtonUsed() //we want the right click to close the poly
085: {
086: return true;
087: }
088:
089: /**
090: * Will return an empty List once the shape is cleared.
091: * @see MultiClickTool#clearShape
092: */
093: public List getCoordinates() {
094: return Collections.unmodifiableList(coordinates);
095: }
096:
097: public void cancelGesture() {
098: //It's important to clear the data when #cancelGesture is called.
099: //Otherwise, you get behaviour like the following:
100: // -- Combine a DragTool with a MultiClickTool using OrCompositeTool
101: // -- Drag a box. A box appears. Release the mouse.
102: // -- Move the mouse. You see a rubber band from MultiClickTool because
103: // the points haven't been cleared. [Jon Aquino]
104: // java.awt.Toolkit.getDefaultToolkit().beep();
105: // if (!altKeyDown)
106: // {
107: super .cancelGesture();
108: coordinates.clear();
109: // }
110: }
111:
112: public void mouseReleased(MouseEvent e) {
113: try {
114: //Can't assert that coordinates is not empty at this point because
115: //of the following situation: NClickTool, n=1, user double-clicks.
116: //Two events are generated: clickCount=1 and clickCount=2.
117: //When #mouseReleased is called with the clickCount=1 event,
118: //coordinates is not empty. But then #finishGesture is called and the
119: //coordinates are cleared. When #mouseReleased is then called with
120: //the clickCount=2 event, coordinates is empty! [Jon Aquino]
121:
122: //Even though drawing is done in #mouseLocationChanged, call it here
123: //also so that #isGestureInProgress returns true on a mouse click.
124: //This is mainly for the benefit of OrCompositeTool, which
125: //calls #isGestureInProgress. [Jon Aquino]
126: //Can't do this in #mouseClicked because #finishGesture may be called
127: //by #mouseReleased (below), which happens before #mouseClicked,
128: //resulting in an IndexOutOfBoundsException in #redrawShape. [Jon Aquino]
129: if (e.getClickCount() == 1) {
130: //A double-click will generate two events: one with click-count = 1 and
131: //another with click-count = 2. Handle the click-count = 1 event and
132: //ignore the rest. Otherwise, the following problem can occur:
133: // -- A click-count = 1 event is generated; #redrawShape is called
134: // -- #isFinishingClick returns true; #finishGesture is called
135: // -- #finishGesture clears the points
136: // -- A click-count = 2 event is generated; #redrawShape is called.
137: // An IndexOutOfBoundsException is thrown because points is empty.
138: //[Jon Aquino]
139: tentativeCoordinate = doConstraint(e);
140: redrawShape();
141: }
142:
143: super .mouseReleased(e);
144:
145: //Check for finish at #mouseReleased rather than #mouseClicked.
146: //#mouseReleased is a more general condition, as it applies to both
147: //drags and clicks. [Jon Aquino]
148: if (isFinishingRelease(e)) {
149: finishGesture();
150: }
151: } catch (Throwable t) {
152: getPanel().getContext().handleThrowable(t);
153: }
154: }
155:
156: protected Coordinate doConstraint(MouseEvent e)
157: throws NoninvertibleTransformException {
158: Coordinate retPt = snap(e.getPoint());
159: retPt = constraintManager.constrain(getPanel(),
160: getCoordinates(), retPt, e);
161: return retPt;
162: }
163:
164: protected void mouseLocationChanged(MouseEvent e) {
165: // if (!altKeyDown) //do this so that we don't get feed back on shape when alt down
166: // {
167: try {
168: if (coordinates.isEmpty())
169: return;
170:
171: tentativeCoordinate = doConstraint(e);
172: Coordinate startPt = (Coordinate) coordinates
173: .get(coordinates.size() - 1);
174: double length = startPt.distance(tentativeCoordinate);
175: double angle = constraintManager.getBearing(startPt,
176: tentativeCoordinate);
177: DecimalFormat df2 = new DecimalFormat("##0.0#");
178: DecimalFormat df3 = new DecimalFormat("###,###,##0.0##");
179: //getPanel().getContext().setStatusMessage("length = " + df3.format(length) + "; angle = " + df2.format(angle) + " degrees");
180: getPanel().getContext().setStatusMessage(
181: lengthST + ": " + df3.format(length) + "; "
182: + angleST + ": " + df2.format(angle) + " "
183: + degrees);
184: // double length = Math.round(startPt.distance(tentativeCoordinate) * 1000.0) / 1000.0;
185: // double angle = Math.round(constraintManager.getBearing(startPt, tentativeCoordinate) * 100.0) / 100.0;
186: // getPanel().getContext().setStatusMessage(" length = " + length + "; angle = " + angle + " degrees");
187: redrawShape();
188: } catch (Throwable t) {
189: getPanel().getContext().handleThrowable(t);
190: }
191: // }
192: }
193:
194: public void mouseMoved(MouseEvent e) {
195: mouseLocationChanged(e);
196: }
197:
198: public void mouseDragged(MouseEvent e) {
199: mouseLocationChanged(e);
200: }
201:
202: protected void add(Coordinate c) {
203: coordinates.add(c);
204: }
205:
206: public void mousePressed(MouseEvent e) {
207: try {
208: super .mousePressed(e);
209: Assert.isTrue(e.getClickCount() > 0);
210:
211: //Don't add more than one point for double-clicks. A double-click will
212: //generate two events: one with click-count = 1 and another with
213: //click-count = 2. Handle the click-count = 1 event and ignore the rest.
214: //[Jon Aquino]
215: if (e.getClickCount() != 1) {
216: return;
217: }
218:
219: add(doConstraint(e));
220:
221: } catch (Throwable t) {
222: getPanel().getContext().handleThrowable(t);
223: }
224: }
225:
226: protected Shape getShape() throws NoninvertibleTransformException {
227: Point2D firstPoint = getPanel().getViewport().toViewPoint(
228: (Coordinate) coordinates.get(0));
229: GeneralPath path = new GeneralPath();
230: path.moveTo((float) firstPoint.getX(), (float) firstPoint
231: .getY());
232:
233: for (int i = 1; i < coordinates.size(); i++) { //start 1 [Jon Aquino]
234:
235: Coordinate nextCoordinate = (Coordinate) coordinates.get(i);
236: Point2D nextPoint = getPanel().getViewport().toViewPoint(
237: nextCoordinate);
238: path.lineTo((int) nextPoint.getX(), (int) nextPoint.getY());
239: }
240: Point2D tentativePoint = getPanel().getViewport().toViewPoint(
241: tentativeCoordinate);
242: path.lineTo((int) tentativePoint.getX(), (int) tentativePoint
243: .getY());
244: if (drawClosed)
245: path.lineTo((float) firstPoint.getX(), (float) firstPoint
246: .getY());
247:
248: return path;
249: }
250:
251: protected boolean isFinishingRelease(MouseEvent e) {
252: return ((e.getClickCount() == 2) || (e.getButton() == MouseEvent.BUTTON3));
253: }
254:
255: protected Coordinate[] toArray(List coordinates) {
256: return (Coordinate[]) coordinates.toArray(new Coordinate[] {});
257: }
258:
259: protected void finishGesture() throws Exception {
260: clearShape();
261:
262: try {
263: fireGestureFinished();
264: } finally {
265: //If exception occurs, cancel. [Jon Aquino]
266: coordinates.clear();
267: }
268: }
269:
270: public void deactivate() {
271: super .deactivate();
272: if (frame != null)
273: frame.removeEasyKeyListener(keyListener);
274: }
275:
276: protected Coordinate getIntersection(Coordinate p1, Coordinate p2,
277: Coordinate p3, Coordinate p4) //find intersection of two lines
278: {
279: Coordinate e = new Coordinate(0, 0);
280: Coordinate v = new Coordinate(0, 0);
281: Coordinate w = new Coordinate(0, 0);
282:
283: double t1 = 0;
284: double n = 0;
285: double d = 0;
286:
287: v.x = p2.x - p1.x;
288: v.y = p2.y - p1.y;
289:
290: w.x = p4.x - p3.x;
291: w.y = p4.y - p3.y;
292:
293: n = w.y * (p3.x - p1.x) - w.x * (p3.y - p1.y);
294: d = w.y * v.x - w.x * v.y; //determinant of 2x2 matrix with v and w
295:
296: if (d != 0.0) //zero only if lines are parallel}
297: {
298: t1 = n / d;
299: e.x = p1.x + v.x * t1;
300: e.y = p1.y + v.y * t1;
301: } else //lines are parallel
302: {
303: e.z = 999; //make not equal to zero to show that lines are parallel
304: }
305: return e;
306: }
307:
308: public void activate(LayerViewPanel layerViewPanel) {
309: super .activate(layerViewPanel);
310: constraintManager = new ConstraintManager(getWorkbench()
311: .getContext());
312:
313: //following added to handle Backspace key deletes last vertex
314: panel = layerViewPanel;
315: frame = AbstractCursorTool.workbenchFrame(panel);
316:
317: if (frame != null)
318: frame.addEasyKeyListener(keyListener);
319: }
320:
321: private KeyListener keyListener = new KeyListener() {
322: public void keyTyped(KeyEvent e) {
323: }
324:
325: public void keyPressed(KeyEvent e) {
326: }
327:
328: public void keyReleased(KeyEvent e) {
329: if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
330: if (coordinates.size() > 1)
331: coordinates.remove(coordinates.size() - 1);
332: panel.repaint();
333: }
334: }
335: };
336: }
|