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: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032:
033: package com.vividsolutions.jump.workbench.ui.cursortool;
034:
035: import java.awt.Shape;
036: import java.awt.event.KeyEvent;
037: import java.awt.event.KeyListener;
038: import java.awt.event.MouseEvent;
039: import java.awt.geom.GeneralPath;
040: import java.awt.geom.NoninvertibleTransformException;
041: import java.awt.geom.Point2D;
042: import java.util.ArrayList;
043: import java.util.Collections;
044: import java.util.List;
045:
046: import com.vividsolutions.jts.geom.Coordinate;
047: import com.vividsolutions.jts.util.Assert;
048: import com.vividsolutions.jump.workbench.ui.LayerViewPanel;
049: import com.vividsolutions.jump.workbench.ui.WorkbenchFrame;
050:
051: /**
052: * A VisualIndicatorTool that allows the user to draw shapes with multiple
053: * vertices. Double-clicking ends the gesture.
054: */
055: public abstract class MultiClickTool extends AbstractCursorTool {
056:
057: //24.iii.03 Dropped drag handling because it's too easy to do a micro-drag when
058: //we mean a click. [Jon Aquino]
059:
060: private List coordinates = new ArrayList();
061: private Coordinate tentativeCoordinate;
062: // set this to true if rubber band should be closed
063: private boolean closeRing = false;
064: private CoordinateListMetrics metrics = null;
065: private LayerViewPanel panel;
066: private WorkbenchFrame frame;
067: private boolean activated = false; //LDB: prevent multiple activate
068:
069: public MultiClickTool() {
070: }
071:
072: protected void setMetricsDisplay(CoordinateListMetrics metrics) {
073: this .metrics = metrics;
074: }
075:
076: protected CoordinateListMetrics getMetrics() {
077: return metrics;
078: }
079:
080: protected void setCloseRing(boolean closeRing) {
081: this .closeRing = closeRing;
082: }
083:
084: /**
085: * Will return an empty List once the shape is cleared.
086: * @see MultiClickTool#clearShape
087: */
088: public List getCoordinates() {
089: return Collections.unmodifiableList(coordinates);
090: }
091:
092: public void cancelGesture() {
093: //It's important to clear the data when #cancelGesture is called.
094: //Otherwise, you get behaviour like the following:
095: // -- Combine a DragTool with a MultiClickTool using OrCompositeTool
096: // -- Drag a box. A box appears. Release the mouse.
097: // -- Move the mouse. You see a rubber band from MultiClickTool because
098: // the points haven't been cleared. [Jon Aquino]
099: super .cancelGesture();
100: coordinates.clear();
101: }
102:
103: public void mouseReleased(MouseEvent e) {
104: try {
105: //Can't assert that coordinates is not empty at this point because
106: //of the following situation: NClickTool, n=1, user double-clicks.
107: //Two events are generated: clickCount=1 and clickCount=2.
108: //When #mouseReleased is called with the clickCount=1 event,
109: //coordinates is not empty. But then #finishGesture is called and the
110: //coordinates are cleared. When #mouseReleased is then called with
111: //the clickCount=2 event, coordinates is empty! [Jon Aquino]
112:
113: //Even though drawing is done in #mouseLocationChanged, call it here
114: //also so that #isGestureInProgress returns true on a mouse click.
115: //This is mainly for the benefit of OrCompositeTool, which
116: //calls #isGestureInProgress. [Jon Aquino]
117: //Can't do this in #mouseClicked because #finishGesture may be called
118: //by #mouseReleased (below), which happens before #mouseClicked,
119: //resulting in an IndexOutOfBoundsException in #redrawShape. [Jon Aquino]
120: if (e.getClickCount() == 1) {
121: //A double-click will generate two events: one with click-count = 1 and
122: //another with click-count = 2. Handle the click-count = 1 event and
123: //ignore the rest. Otherwise, the following problem can occur:
124: // -- A click-count = 1 event is generated; #redrawShape is called
125: // -- #isFinishingClick returns true; #finishGesture is called
126: // -- #finishGesture clears the points
127: // -- A click-count = 2 event is generated; #redrawShape is called.
128: // An IndexOutOfBoundsException is thrown because points is empty.
129: //[Jon Aquino]
130: tentativeCoordinate = snap(e.getPoint());
131: redrawShape();
132: }
133:
134: super .mouseReleased(e);
135:
136: //Check for finish at #mouseReleased rather than #mouseClicked.
137: //#mouseReleased is a more general condition, as it applies to both
138: //drags and clicks. [Jon Aquino]
139: if (isFinishingRelease(e)) {
140: finishGesture();
141: }
142: } catch (Throwable t) {
143: getPanel().getContext().handleThrowable(t);
144: }
145: }
146:
147: protected void mouseLocationChanged(MouseEvent e) {
148: try {
149: if (coordinates.isEmpty()) {
150: return;
151: }
152:
153: tentativeCoordinate = snap(e.getPoint());
154: redrawShape();
155: displayMetrics(e);
156: } catch (Throwable t) {
157: getPanel().getContext().handleThrowable(t);
158: }
159: }
160:
161: private void displayMetrics(MouseEvent e)
162: throws NoninvertibleTransformException {
163: if (metrics == null)
164: return;
165: if (isShapeOnScreen()) {
166: ArrayList currentCoordinates = new ArrayList(
167: getCoordinates());
168: currentCoordinates.add(getPanel().getViewport()
169: .toModelCoordinate(e.getPoint()));
170: metrics.displayMetrics(currentCoordinates, getPanel());
171: }
172: }
173:
174: public void mouseMoved(MouseEvent e) {
175: mouseLocationChanged(e);
176: }
177:
178: public void mouseDragged(MouseEvent e) {
179: mouseLocationChanged(e);
180: }
181:
182: protected void add(Coordinate c) {
183: coordinates.add(c);
184: }
185:
186: public void mousePressed(MouseEvent e) {
187: try {
188: super .mousePressed(e);
189: Assert.isTrue(e.getClickCount() > 0);
190:
191: //Don't add more than one point for double-clicks. A double-click will
192: //generate two events: one with click-count = 1 and another with
193: //click-count = 2. Handle the click-count = 1 event and ignore the rest.
194: //[Jon Aquino]
195: if (e.getClickCount() != 1) {
196: return;
197: }
198:
199: add(snap(e.getPoint()));
200: } catch (Throwable t) {
201: getPanel().getContext().handleThrowable(t);
202: }
203: }
204:
205: protected Shape getShape() throws NoninvertibleTransformException {
206: Point2D firstPoint = getPanel().getViewport().toViewPoint(
207: (Coordinate) coordinates.get(0));
208: GeneralPath path = new GeneralPath();
209: path.moveTo((float) firstPoint.getX(), (float) firstPoint
210: .getY());
211:
212: for (int i = 1; i < coordinates.size(); i++) { //start 1 [Jon Aquino]
213:
214: Coordinate nextCoordinate = (Coordinate) coordinates.get(i);
215: Point2D nextPoint = getPanel().getViewport().toViewPoint(
216: nextCoordinate);
217: path.lineTo((int) nextPoint.getX(), (int) nextPoint.getY());
218: }
219: Point2D tentativePoint = getPanel().getViewport().toViewPoint(
220: tentativeCoordinate);
221: path.lineTo((int) tentativePoint.getX(), (int) tentativePoint
222: .getY());
223: // close path (for rings only)
224: if (closeRing)
225: path.lineTo((int) firstPoint.getX(), (int) firstPoint
226: .getY());
227:
228: return path;
229: }
230:
231: protected boolean isFinishingRelease(MouseEvent e) {
232: return e.getClickCount() == 2;
233: }
234:
235: protected Coordinate[] toArray(List coordinates) {
236: return (Coordinate[]) coordinates.toArray(new Coordinate[] {});
237: }
238:
239: protected void finishGesture() throws Exception {
240: clearShape();
241:
242: try {
243: fireGestureFinished();
244: } finally {
245: //If exception occurs, cancel. [Jon Aquino]
246: coordinates.clear();
247: }
248: }
249:
250: //-- [sstein: 24Mar2007] added for to allow to cancel last vertex per backspace
251: public void deactivate() {
252: super .deactivate();
253: if (frame != null) {
254: frame.removeEasyKeyListener(keyListener);
255: activated = false;
256: }
257: }
258:
259: //-- [sstein: 24Mar2007] added for to allow to cancel last vertex per backspace
260: public void activate(LayerViewPanel layerViewPanel) {
261: super .activate(layerViewPanel);
262:
263: //following added to handle Backspace key deletes last vertex
264: panel = layerViewPanel;
265: frame = AbstractCursorTool.workbenchFrame(panel);
266:
267: if ((frame != null) & (!activated)) { //LDB: prevent multiple activate
268: frame.addEasyKeyListener(keyListener);
269: activated = true;
270: }
271: }
272:
273: private KeyListener keyListener = new KeyListener() {
274: public void keyTyped(KeyEvent e) {
275: }
276:
277: public void keyPressed(KeyEvent e) {
278: }
279:
280: public void keyReleased(KeyEvent e) {
281: if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
282: if (coordinates.size() > 1)
283: coordinates.remove(coordinates.size() - 1);
284: panel.repaint();
285: }
286: }
287: };
288: }
|