/*
* Scale.java
*
* Created on May 31, 2006, 11:15 AM
*
*/
package ika.map.tools;
import ika.geo.*;
import ika.gui.MapComponent;
import ika.gui.SelectionBox;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
/**
* A tool that interactively rotates selected objects.
* @author Bernhard Jenny, Institute of Cartography, ETH Zurich.
*/
public class RotateTool extends SelectionEditingTool implements CombinableTool {
/**
* Mouse must be closer to RADIUS from a corner point to start a rotation.
*/
private static final double RADIUS = 10;
/**
* Minimum angle for transformation to take place.
*/
private static final double MIN_ANGLE = Math.toRadians(0.1);
/** Creates a new instance of SelectionMoveScaleTool */
public RotateTool(MapComponent mapComponent) {
super(mapComponent);
}
/**
* The mouse starts a drag, while this MapTool was the active one.
* @param point The location of the mouse in world coordinates.
* @param evt The original event.
*/
public void startDrag(Point2D.Double point, MouseEvent evt) {
final double mapScale = this.mapComponent.getScaleFactor();
// test if there are any selected GeoObjects and find bounding box of them
this.initialSelectionBox = this.mapComponent.getBoundingBoxOfSelectedGeoObjects();
if (this.initialSelectionBox == null)
return;
this.startPoint = this.searchStartPosition(point);
if (this.startPoint == null) {
this.initialSelectionBox = null;
return;
}
this.captureBackground();
// remember the start position of the drag
this.startPoint = (Point2D.Double)point.clone();
ika.utils.CursorUtils.setCursor("rotate", mapComponent);
}
/**
* Returns the corner point of the selection box close to the passed point.
* @param point Point from which to search the closest point of the selection box.
* @param selectionBox The bounding box around the selected objects.
* @return The closest corner point of the selection box. Null if the passed
* point is too far.
*/
private Point2D.Double searchStartPosition(Point2D point, Rectangle2D selectionBox) {
if (selectionBox == null || point == null)
return null;
// don't accept points inside the selection rectangle
if (selectionBox.contains(point))
return null;
// don't accept points inside the selection handles
final double mapScale = this.mapComponent.getScaleFactor();
int handleID = SelectionBox.findBoxHandle(point, selectionBox, mapScale);
if (handleID != SelectionBox.SELECTION_HANDLE_NONE)
return null;
final double xMin = selectionBox.getMinX();
final double xMax = selectionBox.getMaxX();
final double yMin = selectionBox.getMinY();
final double yMax = selectionBox.getMaxY();
final double SQUARE_RADIUS = RADIUS * RADIUS / (mapScale * mapScale);
final double dll = point.distanceSq(xMin, yMin);
final double dlr = point.distanceSq(xMax, yMin);
final double dur = point.distanceSq(xMax, yMax);
final double dul = point.distanceSq(xMin, yMax);
// find closest corner point of the selection box
if (dll < SQUARE_RADIUS && dll < dlr && dll < dur && dll < dul)
return new Point2D.Double(xMin, yMin); // lower left
else if (dlr < SQUARE_RADIUS && dlr < dll && dlr < dur && dlr < dul)
return new Point2D.Double(xMax, yMin); // lower right
else if (dur < SQUARE_RADIUS && dur < dll && dur < dlr && dur < dul)
return new Point2D.Double(xMax, yMax); // upper right
else if (dul < SQUARE_RADIUS && dul < dll && dul < dlr && dul < dur)
return new Point2D.Double(xMin, yMax); // upper left
return null;
}
/**
* Returns the corner point of the selection box close to the passed point.
* @param point Point from which to search the closest point of the selection box.
* @return The closest corner point of the selection box. Null if the passed
* point is too far.
*/
private Point2D.Double searchStartPosition(Point2D point) {
if (this.initialSelectionBox == null)
return null;
return this.searchStartPosition(point, this.initialSelectionBox);
}
private double computeRotationAngle(Point2D.Double point, MouseEvent evt) {
final double cx = this.initialSelectionBox.getCenterX();
final double cy = this.initialSelectionBox.getCenterY();
double angle = ika.utils.GeometryUtils.angle(
point.getX(), point.getY(),
cx, cy,
this.startPoint.getX(), this.startPoint.getY());
if (evt.isShiftDown()) {
if (angle < 0)
angle += 2 * Math.PI;
angle += Math.PI / 8;
final double PI_4 = Math.PI / 4;
final int id = (int)(angle/ PI_4);
angle = id * PI_4;
}
return angle;
}
protected AffineTransform computeTransform(Point2D.Double point, MouseEvent evt) {
final double cx = initialSelectionBox.getCenterX();
final double cy = initialSelectionBox.getCenterY();
AffineTransform t = new AffineTransform();
t.translate(cx, cy);
final double angle = this.computeRotationAngle(point, evt);
if (Math.abs(angle) < MIN_ANGLE)
return null;
t.rotate(angle);
t.translate(-cx, -cy);
return t;
}
/**
* A drag ends, while this MapTool was the active one.
* @param point The location of the mouse in world coordinates.
* @param evt The original event.
*/
public void endDrag(Point2D.Double point, MouseEvent evt) {
try {
// Test whether the objects were altered during the dragging.
if (!this.isDragging() || !this.differentFromStartPoint(point))
return;
// apply the resulting transformation on the selected GeoObjects
AffineTransform trans = this.computeTransform(point, evt);
if (trans == null)
return;
this.mapComponent.transformSelectedGeoObjects(trans);
this.mapComponent.addUndo("Rotate");
} finally {
this.release();
}
}
/**
* The mouse moved, while this MapTool was the active one.
* @param point The location of the mouse in world coordinates.
*/
public boolean adjustCursor(Point2D.Double point) {
Rectangle2D selectionBox =
this.mapComponent.getBoundingBoxOfSelectedGeoObjects();
if(this.searchStartPosition(point, selectionBox) == null)
return false;
ika.utils.CursorUtils.setCursor("rotate", mapComponent);
return true;
}
}