/*
* The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
* for visualizing and manipulating spatial features with geometry and attributes.
*
* Copyright (C) 2003 Vivid Solutions
*
* 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:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.vividsolutions.jump.workbench.ui.cursortool;
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.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.util.Assert;
import com.vividsolutions.jump.workbench.ui.LayerViewPanel;
import com.vividsolutions.jump.workbench.ui.WorkbenchFrame;
/**
* A VisualIndicatorTool that allows the user to draw shapes with multiple
* vertices. Double-clicking ends the gesture.
*/
public abstract class MultiClickTool 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]
private List coordinates = new ArrayList();
private Coordinate tentativeCoordinate;
// set this to true if rubber band should be closed
private boolean closeRing = false;
private CoordinateListMetrics metrics = null;
private LayerViewPanel panel;
private WorkbenchFrame frame;
private boolean activated = false; //LDB: prevent multiple activate
public MultiClickTool() {
}
protected void setMetricsDisplay(CoordinateListMetrics metrics)
{
this.metrics = metrics;
}
protected CoordinateListMetrics getMetrics() { return metrics; }
protected void setCloseRing(boolean closeRing)
{
this.closeRing = closeRing;
}
/**
* 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]
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 = snap(e.getPoint());
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 void mouseLocationChanged(MouseEvent e) {
try {
if (coordinates.isEmpty()) {
return;
}
tentativeCoordinate = snap(e.getPoint());
redrawShape();
displayMetrics(e);
} catch (Throwable t) {
getPanel().getContext().handleThrowable(t);
}
}
private void displayMetrics(MouseEvent e)
throws NoninvertibleTransformException
{
if (metrics == null) return;
if (isShapeOnScreen()) {
ArrayList currentCoordinates = new ArrayList(getCoordinates());
currentCoordinates.add(snap(getPanel().getViewport().toModelCoordinate(e.getPoint())));
metrics.displayMetrics(currentCoordinates, getPanel());
}
}
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(snap(e.getPoint()));
} catch (Throwable t) {
getPanel().getContext().handleThrowable(t);
}
}
protected Shape getShape() throws NoninvertibleTransformException {
GeneralPath path = new GeneralPath();
// sometimes the coordinates are empty and we get an IndexOutOfBoundsExeption!
// we get this if we use this tool, open a menu and then click with the
// open menu in the map. In this moment we do not get the mousePressed
// event and no coordinate will be added.
if (!coordinates.isEmpty()) {
Point2D firstPoint = getPanel().getViewport().toViewPoint((Coordinate)coordinates.get(0));
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());
// close path (for rings only)
if (closeRing)
path.lineTo((int) firstPoint.getX(), (int) firstPoint.getY());
}
return path;
}
protected boolean isFinishingRelease(MouseEvent e) {
return e.getClickCount() == 2;
}
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();
}
}
//-- [sstein: 24Mar2007] added for to allow to cancel last vertex per backspace
public void deactivate()
{
super.deactivate();
if (frame != null) {
frame.removeEasyKeyListener(keyListener);
activated = false;
}
}
//-- [sstein: 24Mar2007] added for to allow to cancel last vertex per backspace
public void activate(LayerViewPanel layerViewPanel)
{
super.activate(layerViewPanel);
//following added to handle Backspace key deletes last vertex
panel = layerViewPanel;
frame = AbstractCursorTool.workbenchFrame(panel);
if ((frame != null) & (!activated)) { //LDB: prevent multiple activate
frame.addEasyKeyListener(keyListener);
activated = true;
}
}
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();
}
}
};
}