/*
* SelectionBox.java
*
* Created on May 31, 2006, 5:06 PM
*
*/
package ika.gui;
import ika.geo.GeoPath;
import ika.geo.RenderParams;
import ika.utils.ColorUtils;
import java.awt.geom.*;
import java.awt.*;
/**
*
* @author Bernhard Jenny, Institute of Cartography, ETH Zurich.
*/
public class SelectionBox {
public static final int SELECTION_HANDLE_NONE = -1;
// counter clock-wise direction starting with lower left handle
public static final int SELECTION_HANDLE_LOWER_LEFT = 0;
public static final int SELECTION_HANDLE_LOWER_RIGHT = 1;
public static final int SELECTION_HANDLE_UPPER_RIGHT = 2;
public static final int SELECTION_HANDLE_UPPER_LEFT = 3;
public static final int SELECTION_HANDLE_UPPER_CENTER = 4;
public static final int SELECTION_HANDLE_LOWER_CENTER = 5;
public static final int SELECTION_HANDLE_LEFT_CENTER = 6;
public static final int SELECTION_HANDLE_RIGHT_CENTER = 7;
public static final double HANDLE_BOX_SIZE = 6;
public static final double SELECTED_STROKE_WIDTH = 0.5;
public static Rectangle2D[] getSelectionHandles(Rectangle2D selectionBox,
double mapScale) {
if (selectionBox == null) {
return null;
}
Rectangle2D[] handles = new Rectangle2D[8];
final double minX = selectionBox.getMinX();
final double minY = selectionBox.getMinY();
final double maxX = selectionBox.getMaxX();
final double maxY = selectionBox.getMaxY();
final double centerX = (minX + maxX) / 2;
final double centerY = (minY + maxY) / 2;
final double handleDim = HANDLE_BOX_SIZE / mapScale;
final double halfHandleDim = handleDim / 2.;
handles[SELECTION_HANDLE_LOWER_LEFT] = new Rectangle2D.Double(
minX - halfHandleDim, minY - halfHandleDim, handleDim, handleDim);
handles[SELECTION_HANDLE_LOWER_RIGHT] = new Rectangle2D.Double(
maxX - halfHandleDim, minY - halfHandleDim, handleDim, handleDim);
handles[SELECTION_HANDLE_UPPER_RIGHT] = new Rectangle2D.Double(
maxX - halfHandleDim, maxY - halfHandleDim, handleDim, handleDim);
handles[SELECTION_HANDLE_UPPER_LEFT] = new Rectangle2D.Double(
minX - halfHandleDim, maxY - halfHandleDim, handleDim, handleDim);
handles[SELECTION_HANDLE_UPPER_CENTER] = new Rectangle2D.Double(
centerX - halfHandleDim, maxY - halfHandleDim, handleDim, handleDim);
handles[SELECTION_HANDLE_LOWER_CENTER] = new Rectangle2D.Double(
centerX - halfHandleDim, minY - halfHandleDim, handleDim, handleDim);
handles[SELECTION_HANDLE_LEFT_CENTER] = new Rectangle2D.Double(
minX - halfHandleDim, centerY - halfHandleDim, handleDim, handleDim);
handles[SELECTION_HANDLE_RIGHT_CENTER] = new Rectangle2D.Double(
maxX - halfHandleDim, centerY - halfHandleDim, handleDim, handleDim);
return handles;
}
public static void paintSelectionBox(Rectangle2D selectionBox,
RenderParams rp, boolean drawHandles) {
drawRectangle(selectionBox, rp, drawHandles, 0);
}
/**
* Draw a rectangle
* @param selectionBox The rectangle to draw.
* @param rp The render parameters for the map.
* @param drawHandles If true, handles are drawn at the corner of the rectangle.
* @param dashLength If larger than 0, a dashed line is used.
*/
public static void drawRectangle(Rectangle2D box,
RenderParams rp, boolean drawHandles, int dashLength) {
// don't test for selectionBox.isEmpty() which returns true if
// the width or height are negative!
if (box == null || rp.g2d == null) {
return;
}
rp.g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
rp.g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_SPEED);
// make sure the rectangle has positive width and height for drawing
double w = box.getWidth();
double h = box.getHeight();
if (w < 0 || h < 0) {
final double x = box.getX();
final double y = box.getY();
final Rectangle2D rect;
if (w < 0 && h < 0) {
w = Math.abs(w);
h = Math.abs(h);
box = new Rectangle2D.Double(x - w, y - h, w, h);
} else if (w < 0) {
w = Math.abs(w);
box = new Rectangle2D.Double(x - w, y, w, h);
} else {
h = Math.abs(h);
box = new Rectangle2D.Double(x, y - h, w, h);
}
}
if (dashLength > 0) {
// drawing individual lines reduces the "marching ants effect".
drawRectangleAsLines(box, rp, dashLength);
} else {
GeoPath p = GeoPath.newRect(box);
initSymbol(p, ColorUtils.getHighlightColor(), null, dashLength);
p.drawNormalState(rp);
}
// draw handles
if (drawHandles) {
Rectangle2D[] handles = SelectionBox.getSelectionHandles(box, rp.scale);
if (handles != null) {
for (int i = 0; i < handles.length; i++) {
GeoPath p = GeoPath.newRect(handles[i]);
initSymbol(p, ColorUtils.getHighlightColor(), Color.WHITE, 0);
p.drawNormalState(rp);
}
}
}
rp.g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
rp.g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
}
private static void initSymbol(GeoPath p,
Color strokeColor,
Color fillColor,
int dashLength) {
p.getVectorSymbol().setStrokeColor(strokeColor);
// setup stroke. Use a line width of 0 for a thin line. This is ok, see
// java bug #4114921 and #4093921.
p.getVectorSymbol().setStrokeWidth(0);
if (dashLength > 0) {
p.getVectorSymbol().setDashLength(dashLength);
}
if (fillColor != null) {
p.getVectorSymbol().setFilled(true);
p.getVectorSymbol().setFillColor(fillColor);
}
}
/**
* draw 4 lines forming a rectangle. Useful to reduce the marching ants effect.
* @param rect
* @param rp
* @param dashLength
*/
private static void drawRectangleAsLines(Rectangle2D rect, RenderParams rp, int dashLength) {
final double west = rect.getMinX();
final double east = rect.getMaxX();
final double south = rect.getMinY();
final double north = rect.getMaxY();
GeoPath p = new GeoPath();
initSymbol(p, ColorUtils.getHighlightColor(), null, dashLength);
p.moveTo(west, south);
p.lineTo(east, south);
p.drawNormalState(rp);
p.reset();
p.moveTo(east, north);
p.lineTo(east, south);
p.drawNormalState(rp);
p.reset();
p.moveTo(west, north);
p.lineTo(east, north);
p.drawNormalState(rp);
p.reset();
p.moveTo(west, north);
p.lineTo(west, south);
p.drawNormalState(rp);
}
/**
* Returns a handle ID for a passed point.
* @param point The point for which to search the handle ID.
* @selectionBox The bounding box.
* @mapScale The current map scale.
* @return The handle ID between 0 and 7, or SELECTION_HANDLE_NONE if
* the passed point is not on a handle.
*/
public static int findBoxHandle(Point2D point,
Rectangle2D selectionBox, double mapScale) {
Rectangle2D[] handles = SelectionBox.getSelectionHandles(selectionBox, mapScale);
if (handles != null) {
for (int i = 0; i < handles.length; i++) {
if (handles[i].contains(point)) {
return i;
}
}
}
return SELECTION_HANDLE_NONE;
}
public static void adjustSelectionBox(Rectangle2D selectionBox,
int draggedHandle,
Point2D.Double point,
boolean uniformScaling,
double initialRatio) {
final double x = selectionBox.getMinX();
final double y = selectionBox.getMinY();
final double w = selectionBox.getWidth();
final double h = selectionBox.getHeight();
final double px = point.getX();
final double py = point.getY();
double newX = x;
double newY = y;
double newW = w;
double newH = h;
switch (draggedHandle) {
// corner handles
case SelectionBox.SELECTION_HANDLE_LOWER_LEFT:
newX = px;
newY = py;
newW = w + x - px;
newH = h + y - py;
break;
case SelectionBox.SELECTION_HANDLE_LOWER_RIGHT:
newY = py;
newW = px - x;
newH = h + y - py;
break;
case SelectionBox.SELECTION_HANDLE_UPPER_RIGHT:
newW = px - x;
newH = py - y;
break;
case SelectionBox.SELECTION_HANDLE_UPPER_LEFT:
newX = px;
newW = w + x - px;
newH = py - y;
break;
// central handles
case SelectionBox.SELECTION_HANDLE_UPPER_CENTER:
newH = py - y;
break;
case SelectionBox.SELECTION_HANDLE_LOWER_CENTER:
newY = py;
newH = h + y - py;
break;
case SelectionBox.SELECTION_HANDLE_LEFT_CENTER:
newX = px;
newW = w + x - px;
break;
case SelectionBox.SELECTION_HANDLE_RIGHT_CENTER:
newW = px - x;
break;
}
if (uniformScaling) {
final double wRatio = newW / w;
final double hRatio = newH / h;
if (wRatio < hRatio) {
newH = newW / initialRatio;
} else {
newW = initialRatio * newH;
}
}
selectionBox.setFrame(newX, newY, newW, newH);
}
}