/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * JUMP is Copyright (C) 2003 Vivid Solutions * * This program implements extensions to JUMP and is * Copyright (C) 2004 Integrated Systems Analysts, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Integrated Systems Analysts, Inc. * 630C Anchors St., Suite 101 * Fort Walton Beach, Florida * USA * * (850)862-7321 */ package org.openjump.core.ui.plugin.edittoolbox.cursortools; import java.awt.Shape; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.geom.GeneralPath; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.openjump.core.ui.plugin.edittoolbox.tab.ConstraintManager; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.util.Assert; import com.vividsolutions.jump.I18N; import com.vividsolutions.jump.workbench.ui.LayerViewPanel; import com.vividsolutions.jump.workbench.ui.WorkbenchFrame; import com.vividsolutions.jump.workbench.ui.cursortool.AbstractCursorTool; import com.vividsolutions.jump.workbench.ui.cursortool.MultiClickTool; /** * A VisualIndicatorTool that allows the user to draw shapes with multiple * vertices. Double-clicking ends the gesture. */ public abstract class ConstrainedMultiClickTool extends AbstractCursorTool { //24.iii.03 Dropped drag handling because it's too easy to do a micro-drag when //we mean a click. [Jon Aquino] final static String lengthST =I18N.get("org.openjump.core.ui.plugin.edittoolbox.cursortools.length"); final static String angleST =I18N.get("org.openjump.core.ui.plugin.edittoolbox.cursortools.angle"); final static String degrees =I18N.get("org.openjump.core.ui.plugin.edittoolbox.cursortools.degrees"); protected List coordinates = new ArrayList(); protected Coordinate tentativeCoordinate; protected boolean drawClosed = true; private ConstraintManager constraintManager; // private LayerViewPanel panel; private WorkbenchFrame frame; public ConstrainedMultiClickTool() { } public boolean isRightMouseButtonUsed() //we want the right click to close the poly { return true; } /** * Will return an empty List once the shape is cleared. * @see MultiClickTool#clearShape */ public List getCoordinates() { return Collections.unmodifiableList(coordinates); } public void cancelGesture() { //It's important to clear the data when #cancelGesture is called. //Otherwise, you get behaviour like the following: // -- Combine a DragTool with a MultiClickTool using OrCompositeTool // -- Drag a box. A box appears. Release the mouse. // -- Move the mouse. You see a rubber band from MultiClickTool because // the points haven't been cleared. [Jon Aquino] // java.awt.Toolkit.getDefaultToolkit().beep(); // if (!altKeyDown) // { super.cancelGesture(); coordinates.clear(); // } } public void mouseReleased(MouseEvent e) { try { //Can't assert that coordinates is not empty at this point because //of the following situation: NClickTool, n=1, user double-clicks. //Two events are generated: clickCount=1 and clickCount=2. //When #mouseReleased is called with the clickCount=1 event, //coordinates is not empty. But then #finishGesture is called and the //coordinates are cleared. When #mouseReleased is then called with //the clickCount=2 event, coordinates is empty! [Jon Aquino] //Even though drawing is done in #mouseLocationChanged, call it here //also so that #isGestureInProgress returns true on a mouse click. //This is mainly for the benefit of OrCompositeTool, which //calls #isGestureInProgress. [Jon Aquino] //Can't do this in #mouseClicked because #finishGesture may be called //by #mouseReleased (below), which happens before #mouseClicked, //resulting in an IndexOutOfBoundsException in #redrawShape. [Jon Aquino] if (e.getClickCount() == 1) { //A double-click will generate two events: one with click-count = 1 and //another with click-count = 2. Handle the click-count = 1 event and //ignore the rest. Otherwise, the following problem can occur: // -- A click-count = 1 event is generated; #redrawShape is called // -- #isFinishingClick returns true; #finishGesture is called // -- #finishGesture clears the points // -- A click-count = 2 event is generated; #redrawShape is called. // An IndexOutOfBoundsException is thrown because points is empty. //[Jon Aquino] tentativeCoordinate = doConstraint(e); redrawShape(); } super.mouseReleased(e); //Check for finish at #mouseReleased rather than #mouseClicked. //#mouseReleased is a more general condition, as it applies to both //drags and clicks. [Jon Aquino] if (isFinishingRelease(e)) { finishGesture(); } } catch (Throwable t) { getPanel().getContext().handleThrowable(t); } } protected Coordinate doConstraint(MouseEvent e) throws NoninvertibleTransformException { Coordinate retPt = snap(e.getPoint()); retPt = constraintManager.constrain(getPanel(), getCoordinates(), retPt, e); return retPt; } protected void mouseLocationChanged(MouseEvent e) { // if (!altKeyDown) //do this so that we don't get feed back on shape when alt down // { try { if (coordinates.isEmpty()) return; tentativeCoordinate = doConstraint(e); Coordinate startPt = (Coordinate)coordinates.get(coordinates.size() - 1); double length = startPt.distance(tentativeCoordinate); double angle = constraintManager.getBearing(startPt, tentativeCoordinate); DecimalFormat df2 = new DecimalFormat("##0.0#"); DecimalFormat df3 = new DecimalFormat("###,###,##0.0##"); //getPanel().getContext().setStatusMessage("length = " + df3.format(length) + "; angle = " + df2.format(angle) + " degrees"); getPanel().getContext().setStatusMessage(lengthST + ": " + df3.format(length) + "; " + angleST + ": " + df2.format(angle) + " " + degrees); // double length = Math.round(startPt.distance(tentativeCoordinate) * 1000.0) / 1000.0; // double angle = Math.round(constraintManager.getBearing(startPt, tentativeCoordinate) * 100.0) / 100.0; // getPanel().getContext().setStatusMessage(" length = " + length + "; angle = " + angle + " degrees"); redrawShape(); } catch (Throwable t) { getPanel().getContext().handleThrowable(t); } // } } public void mouseMoved(MouseEvent e) { mouseLocationChanged(e); } public void mouseDragged(MouseEvent e) { mouseLocationChanged(e); } protected void add(Coordinate c) { coordinates.add(c); } public void mousePressed(MouseEvent e) { try { super.mousePressed(e); Assert.isTrue(e.getClickCount() > 0); //Don't add more than one point for double-clicks. A double-click will //generate two events: one with click-count = 1 and another with //click-count = 2. Handle the click-count = 1 event and ignore the rest. //[Jon Aquino] if (e.getClickCount() != 1) { return; } add(doConstraint(e)); } catch (Throwable t) { getPanel().getContext().handleThrowable(t); } } protected Shape getShape() throws NoninvertibleTransformException { Point2D firstPoint = getPanel().getViewport().toViewPoint((Coordinate)coordinates.get(0)); GeneralPath path = new GeneralPath(); path.moveTo((float) firstPoint.getX(), (float) firstPoint.getY()); for (int i = 1; i < coordinates.size(); i++) { //start 1 [Jon Aquino] Coordinate nextCoordinate = (Coordinate) coordinates.get(i); Point2D nextPoint = getPanel().getViewport().toViewPoint(nextCoordinate); path.lineTo((int) nextPoint.getX(), (int) nextPoint.getY()); } Point2D tentativePoint = getPanel().getViewport().toViewPoint(tentativeCoordinate); path.lineTo((int) tentativePoint.getX(), (int) tentativePoint.getY()); if (drawClosed) path.lineTo((float) firstPoint.getX(), (float) firstPoint.getY()); return path; } protected boolean isFinishingRelease(MouseEvent e) { return ((e.getClickCount() == 2) || (e.getButton() == MouseEvent.BUTTON3)); } protected Coordinate[] toArray(List coordinates) { return (Coordinate[]) coordinates.toArray(new Coordinate[] { }); } protected void finishGesture() throws Exception { clearShape(); try { fireGestureFinished(); } finally { //If exception occurs, cancel. [Jon Aquino] coordinates.clear(); } } public void deactivate() { super.deactivate(); if (frame != null) frame.removeEasyKeyListener(keyListener); } protected Coordinate getIntersection(Coordinate p1, Coordinate p2, Coordinate p3, Coordinate p4) //find intersection of two lines { Coordinate e = new Coordinate(0,0); Coordinate v = new Coordinate(0,0); Coordinate w = new Coordinate(0,0); double t1 = 0; double n = 0; double d = 0; v.x = p2.x - p1.x; v.y = p2.y - p1.y; w.x = p4.x - p3.x; w.y = p4.y - p3.y; n = w.y * (p3.x - p1.x) - w.x * (p3.y - p1.y); d = w.y * v.x - w.x * v.y; //determinant of 2x2 matrix with v and w if (d != 0.0) //zero only if lines are parallel} { t1 = n / d; e.x = p1.x + v.x * t1; e.y = p1.y + v.y * t1; } else //lines are parallel { e.z = 999; //make not equal to zero to show that lines are parallel } return e; } public void activate(LayerViewPanel layerViewPanel) { super.activate(layerViewPanel); constraintManager = new ConstraintManager(getWorkbench().getContext()); //following added to handle Backspace key deletes last vertex panel = layerViewPanel; frame = AbstractCursorTool.workbenchFrame(panel); if (frame != null) frame.addEasyKeyListener(keyListener); } private KeyListener keyListener = new KeyListener() { public void keyTyped(KeyEvent e) { } public void keyPressed(KeyEvent e) { } public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) { if (coordinates.size() > 1) coordinates.remove(coordinates.size() - 1); panel.repaint(); } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) try { finishGesture(); }catch (Exception ex) { getPanel().getContext().handleThrowable(ex); }; } }; }