/*
* Copyright (C) 2011 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* 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 3 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, see http://www.gnu.org/licenses/
*/
package org.esa.snap.ui;
import com.bc.ceres.binding.PropertySet;
import com.bc.ceres.binding.ValidationException;
import com.bc.ceres.core.Assert;
import com.bc.ceres.glayer.swing.LayerCanvas;
import com.bc.ceres.grender.Rendering;
import com.bc.ceres.grender.Viewport;
import com.bc.ceres.swing.binding.BindingContext;
import com.bc.ceres.swing.figure.Figure;
import com.bc.ceres.swing.figure.FigureCollection;
import com.bc.ceres.swing.figure.FigureEditor;
import com.bc.ceres.swing.figure.FigureEditorAware;
import com.bc.ceres.swing.figure.ShapeFigure;
import com.bc.ceres.swing.figure.ViewportInteractor;
import com.bc.ceres.swing.figure.support.DefaultFigureEditor;
import com.bc.ceres.swing.figure.support.DefaultFigureStyle;
import org.esa.snap.core.datamodel.GeoCoding;
import org.esa.snap.core.datamodel.GeoPos;
import org.esa.snap.core.datamodel.PixelPos;
import org.esa.snap.core.datamodel.Product;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
/**
* This class wraps a {@link WorldMapPane} and extends it by functionality to draw and resize a selection rectangle.
*
* @author Thomas Storm
* @author Tonio Fincke
*/
public class RegionSelectableWorldMapPane {
public static final String NORTH_BOUND = "northBound";
public static final String SOUTH_BOUND = "southBound";
public static final String WEST_BOUND = "westBound";
public static final String EAST_BOUND = "eastBound";
private static final int OFFSET = 6;
private final BindingContext bindingContext;
private final DefaultFigureEditor figureEditor;
private boolean mustInitFigureEditor = true;
private final WorldMapPane worldMapPane;
private final RegionSelectionInteractor regionSelectionInteractor;
private final RegionSelectableWorldMapPane.CursorChanger cursorChanger = new CursorChanger();
private Rectangle2D selectionRectangle;
private Rectangle2D movableRectangle;
private Rectangle2D.Double defaultRectangle;
private Shape defaultShape;
private final RegionSelectableWorldMapPane.RegionSelectionDecoratingPanSupport panSupport;
/**
* Creates a RegionSelectableWorldMapPane.
*
* @param dataModel The data model to be used
* @param bindingContext The binding context which has to contain at least the following properties:
* {@link #NORTH_BOUND northBound} ,
* {@link #SOUTH_BOUND southBound}, {@link #WEST_BOUND westBound}, and
* {@link #EAST_BOUND eastBound}. If all these property values are null, default values
* will be used. The property values are considered valid when the latitude values are
* within the allowed latitude range [-90, 90], the longitude values are within the
* allowed longitude range [-180, 180], the northBound is bigger than the southBound,
* the eastBound is bigger than the westBound, and no value is null. In this case,
* the world map will be initialized with these values.
*
* @throws IllegalArgumentException If the bindingContext is null, it does not contain the expected properties or
* the properties do not contain valid values
*/
public RegionSelectableWorldMapPane(WorldMapPaneDataModel dataModel, BindingContext bindingContext) {
ensureValidBindingContext(bindingContext);
this.bindingContext = bindingContext;
worldMapPane = new FigureEditorAwareWorldMapPane(dataModel, new SelectionOverlay(dataModel));
panSupport = new RegionSelectionDecoratingPanSupport(worldMapPane.getLayerCanvas());
worldMapPane.setPanSupport(panSupport);
figureEditor = new DefaultFigureEditor(worldMapPane.getLayerCanvas());
regionSelectionInteractor = new RegionSelectionInteractor();
worldMapPane.getLayerCanvas().addMouseMotionListener(cursorChanger);
}
public JPanel createUI() {
return worldMapPane;
}
static void ensureValidBindingContext(BindingContext bindingContext) {
if (bindingContext == null) {
throw new IllegalArgumentException("bindingContext must be not null");
}
ensureExistingProperty(bindingContext, NORTH_BOUND);
ensureExistingProperty(bindingContext, SOUTH_BOUND);
ensureExistingProperty(bindingContext, WEST_BOUND);
ensureExistingProperty(bindingContext, EAST_BOUND);
PropertySet propertySet = bindingContext.getPropertySet();
final Double northBound = propertySet.getValue(NORTH_BOUND);
final Double eastBound = propertySet.getValue(EAST_BOUND);
final Double southBound = propertySet.getValue(SOUTH_BOUND);
final Double westBound = propertySet.getValue(WEST_BOUND);
if (northBound == null && eastBound == null && southBound == null && westBound == null) {
setDefaultValues(bindingContext);
} else if (!geoBoundsAreValid(northBound, eastBound, southBound, westBound)) {
throw new IllegalArgumentException(MessageFormat.format("Given geo-bounds ({0}, {1}, {2}, {3}) are invalid.",
northBound, eastBound, southBound, westBound));
}
}
private DefaultFigureEditor getFigureEditor() {
initFigureEditor(figureEditor, null);
return figureEditor;
}
private void initFigureEditor(DefaultFigureEditor editor, Product selectedProduct) {
if (mustInitFigureEditor) {
initGeometries(selectedProduct);
final ShapeFigure polygonFigure = editor.getFigureFactory().createPolygonFigure(defaultShape, createFigureStyle(
(DefaultFigureStyle) editor.getDefaultPolygonStyle()));
if (polygonFigure != null) {
editor.getFigureCollection().addFigure(polygonFigure);
}
regionSelectionInteractor.updateProperties(defaultShape.getBounds2D());
mustInitFigureEditor = false;
}
}
private static void ensureExistingProperty(BindingContext bindingContext, String propertyName) {
Assert.argument(bindingContext.getPropertySet().getProperty(propertyName) != null,
"bindingContext must contain a property named " + propertyName);
}
private static void setDefaultValues(BindingContext bindingContext) {
bindingContext.getPropertySet().setValue(NORTH_BOUND, 75.0);
bindingContext.getPropertySet().setValue(WEST_BOUND, -15.0);
bindingContext.getPropertySet().setValue(SOUTH_BOUND, 35.0);
bindingContext.getPropertySet().setValue(EAST_BOUND, 30.0);
}
static boolean geoBoundsAreValid(Double northBound, Double eastBound, Double southBound, Double westBound) {
return northBound != null
&& eastBound != null
&& southBound != null
&& westBound != null
&& northBound > southBound
&& eastBound > westBound
&& isInValidLatitudeRange(northBound)
&& isInValidLatitudeRange(southBound)
&& isInValidLongitudeRange(eastBound)
&& isInValidLongitudeRange(westBound);
}
static void correctBoundsIfNecessary(Rectangle2D newFigureShape) {
double minX = newFigureShape.getMinX();
double minY = newFigureShape.getMinY();
double maxX = newFigureShape.getMaxX();
double maxY = newFigureShape.getMaxY();
minX = Math.min(maxX, Math.min(180, Math.max(-180, minX)));
minY = Math.min(maxY, Math.min(90, Math.max(-90, minY)));
maxX = Math.max(minX, Math.min(180, Math.max(-180, maxX)));
maxY = Math.max(minY, Math.min(90, Math.max(-90, maxY)));
minX = Math.min(maxX, Math.min(180, Math.max(-180, minX)));
minY = Math.min(maxY, Math.min(90, Math.max(-90, minY)));
if (newFigureShape.getMinX() != minX || newFigureShape.getMinY() != minY
|| newFigureShape.getMaxX() != maxX || newFigureShape.getMaxY() != maxY) {
// there were numerical issues with the direct approach that returned rectangles with a maxX slightly larger
// than 180 deg. To ensure data is always in the valid range, we subtract a small amount from width and height.
// tb 2015-11-02
final double width = maxX - minX;
final double height = maxY - minY;
newFigureShape.setRect(minX, minY, width - 1e-12, height - 1e-12);
}
}
private static boolean isInValidLongitudeRange(Double longitude) {
return longitude <= 180 && longitude >= -180;
}
private static boolean isInValidLatitudeRange(Double latitude) {
return latitude <= 90 && latitude >= -90;
}
private DefaultFigureStyle createFigureStyle(DefaultFigureStyle figureStyle) {
figureStyle.setFillColor(new Color(255, 200, 200));
figureStyle.setFillOpacity(0.2);
figureStyle.setStrokeColor(new Color(200, 0, 0));
figureStyle.setStrokeWidth(2);
return figureStyle;
}
private void updateRectangles() {
AffineTransform modelToView = worldMapPane.getLayerCanvas().getViewport().getModelToViewTransform();
selectionRectangle = modelToView.createTransformedShape(getFirstFigure().getBounds()).getBounds2D();
movableRectangle.setRect(selectionRectangle);
cursorChanger.updateRectanglesForDragCursor();
}
private Figure getFirstFigure() {
return getFigureEditor().getFigureCollection().getFigure(0);
}
private class CursorChanger implements MouseMotionListener {
private final String DEFAULT = "default";
private final String MOVE = "move";
private final String NORTH = "north";
private final String SOUTH = "south";
private final String WEST = "west";
private final String EAST = "east";
private final String NORTH_WEST = "northWest";
private final String NORTH_EAST = "northEast";
private final String SOUTH_WEST = "southWest";
private final String SOUTH_EAST = "southEast";
private Map<String, Cursor> cursorMap;
private Map<String, Rectangle2D.Double> rectangleMap;
private CursorChanger() {
cursorMap = new HashMap<>();
cursorMap.put(DEFAULT, Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
cursorMap.put(MOVE, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
cursorMap.put(NORTH, Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
cursorMap.put(SOUTH, Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
cursorMap.put(WEST, Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
cursorMap.put(EAST, Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
cursorMap.put(NORTH_WEST, Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
cursorMap.put(NORTH_EAST, Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
cursorMap.put(SOUTH_WEST, Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
cursorMap.put(SOUTH_EAST, Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
rectangleMap = new HashMap<>();
rectangleMap.put(DEFAULT, new Rectangle2D.Double());
rectangleMap.put(MOVE, new Rectangle2D.Double());
rectangleMap.put(NORTH, new Rectangle2D.Double());
rectangleMap.put(SOUTH, new Rectangle2D.Double());
rectangleMap.put(WEST, new Rectangle2D.Double());
rectangleMap.put(EAST, new Rectangle2D.Double());
rectangleMap.put(NORTH_EAST, new Rectangle2D.Double());
rectangleMap.put(NORTH_WEST, new Rectangle2D.Double());
rectangleMap.put(SOUTH_EAST, new Rectangle2D.Double());
rectangleMap.put(SOUTH_WEST, new Rectangle2D.Double());
}
@Override
public void mouseDragged(MouseEvent e) {
updateRectanglesForDragCursor();
updateCursor(e);
}
@Override
public void mouseMoved(MouseEvent e) {
updateCursor(e);
}
private void updateCursor(MouseEvent e) {
final boolean cursorOutsideOfSelectionRectangle =
!rectangleMap.get(DEFAULT).contains(e.getPoint()) &&
worldMapPane.getCursor() != cursorMap.get(DEFAULT);
if (cursorOutsideOfSelectionRectangle) {
worldMapPane.setCursor(cursorMap.get(DEFAULT));
} else {
final String[] regionIdentifiers = {
MOVE,
NORTH,
SOUTH,
WEST,
EAST,
NORTH_WEST,
NORTH_EAST,
SOUTH_WEST,
SOUTH_EAST
};
for (String region : regionIdentifiers) {
boolean cursorIsSet = setCursorWhenContained(cursorMap.get(region), rectangleMap.get(region), e.getPoint());
if (cursorIsSet) {
break;
}
}
}
}
private boolean setCursorWhenContained(Cursor cursor, Rectangle2D.Double rectangle, Point point) {
if (rectangle.contains(point)) {
if (worldMapPane.getCursor() != cursor) {
worldMapPane.setCursor(cursor);
}
return true;
}
return false;
}
private void updateRectanglesForDragCursor() {
Rectangle2D.Double rectangleForDragCursor = new Rectangle2D.Double(movableRectangle.getX() + OFFSET,
movableRectangle.getY() + OFFSET,
movableRectangle.getWidth() - 2 * OFFSET,
movableRectangle.getHeight() - 2 * OFFSET);
rectangleMap.get(MOVE).setRect(rectangleForDragCursor);
final double x = rectangleForDragCursor.getX();
final double y = rectangleForDragCursor.getY();
final double width = rectangleForDragCursor.getWidth();
final double height = rectangleForDragCursor.getHeight();
setRectangle(DEFAULT, x - 2 * OFFSET, y - 2 * OFFSET, width + 4 * OFFSET, height + 4 * OFFSET);
setRectangle(NORTH, x, y - 2 * OFFSET, width, 2 * OFFSET);
setRectangle(SOUTH, x, y + height, width, 2 * OFFSET);
setRectangle(WEST, x - 2 * OFFSET, y, 2 * OFFSET, height);
setRectangle(EAST, x + width, y, 2 * OFFSET, height);
setRectangle(NORTH_WEST, x - 2 * OFFSET, y - 2 * OFFSET, 2 * OFFSET, 2 * OFFSET);
setRectangle(NORTH_EAST, x + width, y - 2 * OFFSET, 2 * OFFSET, 2 * OFFSET);
setRectangle(SOUTH_WEST, x - 2 * OFFSET, y + height, 2 * OFFSET, 2 * OFFSET);
setRectangle(SOUTH_EAST, x + width, y + height, 2 * OFFSET, 2 * OFFSET);
}
private void setRectangle(String rectangleIdentifier, double x, double y, double w, double h) {
rectangleMap.get(rectangleIdentifier).setRect(x, y, w, h);
}
}
private class FigureEditorAwareWorldMapPane extends WorldMapPane implements FigureEditorAware {
private LayerCanvas.Overlay greyOverlay;
private FigureEditorAwareWorldMapPane(WorldMapPaneDataModel dataModel, SelectionOverlay overlay) {
super(dataModel, overlay);
addZoomListener(() -> handleZoom());
greyOverlay = (canvas, rendering) -> {
final Graphics2D graphics = rendering.getGraphics();
graphics.setPaint(new Color(200, 200, 200, 180));
graphics.fillRect(0, 0, worldMapPane.getWidth(), worldMapPane.getHeight());
};
}
@Override
public FigureEditor getFigureEditor() {
initFigureEditor(RegionSelectableWorldMapPane.this.figureEditor, null);
return figureEditor;
}
@Override
protected Action[] getOverlayActions() {
final Action[] overlayActions = super.getOverlayActions();
final Action[] actions = new Action[overlayActions.length + 1];
System.arraycopy(overlayActions, 0, actions, 0, overlayActions.length);
actions[actions.length - 1] = new ResetAction();
return actions;
}
@Override
public void zoomToProduct(Product product) {
if (product != null) {
super.zoomToProduct(product);
}
Rectangle2D modelBounds = getFirstFigure().getBounds();
modelBounds.setFrame(modelBounds.getX() - 2, modelBounds.getY() - 2,
modelBounds.getWidth() + 4, modelBounds.getHeight() + 4);
getLayerCanvas().getViewport().zoom(modelBounds);
handleZoom();
}
@Override
public void setEnabled(boolean enabled) {
if (enabled == isEnabled()) {
return;
}
super.setEnabled(enabled);
if (enabled) {
worldMapPane.getLayerCanvas().addMouseMotionListener(cursorChanger);
worldMapPane.getLayerCanvas().removeOverlay(greyOverlay);
worldMapPane.setPanSupport(panSupport);
} else {
worldMapPane.getLayerCanvas().removeMouseMotionListener(cursorChanger);
worldMapPane.getLayerCanvas().addOverlay(greyOverlay);
worldMapPane.setPanSupport(new NullPanSupport());
}
}
private class NullPanSupport implements PanSupport {
@Override
public void panStarted(MouseEvent event) {
}
@Override
public void performPan(MouseEvent event) {
}
@Override
public void panStopped(MouseEvent event) {
}
}
}
private void handleZoom() {
final FigureCollection figureCollection = getFigureEditor().getFigureCollection();
if (figureCollection.getFigureCount() == 0) {
return;
}
final Rectangle2D modelBounds = figureCollection.getFigure(0).getBounds();
final AffineTransform modelToViewTransform = worldMapPane.getLayerCanvas().getViewport().getModelToViewTransform();
final Shape transformedShape = modelToViewTransform.createTransformedShape(modelBounds);
movableRectangle.setRect(transformedShape.getBounds2D());
selectionRectangle.setRect(movableRectangle);
cursorChanger.updateRectanglesForDragCursor();
}
private class SelectionOverlay extends BoundaryOverlay {
protected SelectionOverlay(WorldMapPaneDataModel dataModel) {
super(dataModel);
}
@Override
protected void handleSelectedProduct(Rendering rendering, Product selectedProduct) {
DefaultFigureEditor editor = getFigureEditor();
initFigureEditor(editor, selectedProduct);
editor.drawFigureCollection(rendering);
}
}
private void initGeometries(Product selectedProduct) {
final GeoPos upperLeftGeoPos;
final GeoPos lowerRightGeoPos;
if (selectedProduct != null) {
PixelPos upperLeftPixel = new PixelPos(0.5f, 0.5f);
PixelPos lowerRightPixel = new PixelPos(
selectedProduct.getSceneRasterWidth() - 0.5f, selectedProduct.getSceneRasterHeight() - 0.5f);
GeoCoding geoCoding = selectedProduct.getSceneGeoCoding();
upperLeftGeoPos = geoCoding.getGeoPos(upperLeftPixel, null);
lowerRightGeoPos = geoCoding.getGeoPos(lowerRightPixel, null);
} else {
PropertySet propertySet = bindingContext.getPropertySet();
final Double northBound = propertySet.getValue(NORTH_BOUND);
final Double eastBound = propertySet.getValue(EAST_BOUND);
final Double southBound = propertySet.getValue(SOUTH_BOUND);
final Double westBound = propertySet.getValue(WEST_BOUND);
upperLeftGeoPos = new GeoPos(northBound.floatValue(), westBound.floatValue());
lowerRightGeoPos = new GeoPos(southBound.floatValue(), eastBound.floatValue());
}
Viewport viewport = worldMapPane.getLayerCanvas().getViewport();
AffineTransform modelToViewTransform = viewport.getModelToViewTransform();
Point2D.Double lowerRight = modelToView(lowerRightGeoPos, modelToViewTransform);
Point2D.Double upperLeft = modelToView(upperLeftGeoPos, modelToViewTransform);
Rectangle2D.Double rectangularShape = new Rectangle2D.Double(upperLeft.x, upperLeft.y,
lowerRight.x - upperLeft.x,
lowerRight.y - upperLeft.y);
selectionRectangle = createRectangle(rectangularShape);
movableRectangle = createRectangle(rectangularShape);
defaultRectangle = createRectangle(rectangularShape);
cursorChanger.updateRectanglesForDragCursor();
defaultShape = viewport.getViewToModelTransform().createTransformedShape(rectangularShape);
}
private Rectangle2D.Double createRectangle(Rectangle2D.Double rectangularShape) {
return new Rectangle2D.Double(rectangularShape.getX(), rectangularShape.getY(),
rectangularShape.getWidth(), rectangularShape.getHeight());
}
private Point2D.Double modelToView(GeoPos geoPos, AffineTransform modelToView) {
Point2D.Double result = new Point2D.Double();
modelToView.transform(new Point2D.Double(geoPos.getLon(), geoPos.getLat()), result);
return result;
}
private class RegionSelectionInteractor extends ViewportInteractor {
private static final int NO_LONGITUDE_BORDER = -3;
private static final int NO_LATITUDE_BORDER = -2;
private static final int BORDER_UNKNOWN = -1;
private static final int NORTH_BORDER = 0;
private static final int EAST_BORDER = 1;
private static final int SOUTH_BORDER = 2;
private static final int WEST_BORDER = 3;
private Point point;
private int rectangleLongitude;
private int rectangleLatitude;
private boolean rectangleIsCurrentlyDrawn;
private RegionSelectionInteractor() {
PropertySet propertySet = bindingContext.getPropertySet();
propertySet.getProperty(NORTH_BOUND).addPropertyChangeListener(new BoundsChangeListener(NORTH_BOUND));
propertySet.getProperty(SOUTH_BOUND).addPropertyChangeListener(new BoundsChangeListener(SOUTH_BOUND));
propertySet.getProperty(WEST_BOUND).addPropertyChangeListener(new BoundsChangeListener(WEST_BOUND));
propertySet.getProperty(EAST_BOUND).addPropertyChangeListener(new BoundsChangeListener(EAST_BOUND));
rectangleIsCurrentlyDrawn = false;
}
@Override
public void mousePressed(MouseEvent event) {
point = event.getPoint();
determineDraggedRectangleBorders(event);
}
private void determineDraggedRectangleBorders(MouseEvent e) {
double x = e.getX();
double y = e.getY();
double x1 = selectionRectangle.getX();
double y1 = selectionRectangle.getY();
double x2 = x1 + selectionRectangle.getWidth();
double y2 = y1 + selectionRectangle.getHeight();
double dx1 = Math.abs(x1 - x);
double dy1 = Math.abs(y1 - y);
double dx2 = Math.abs(x2 - x);
double dy2 = Math.abs(y2 - y);
rectangleLongitude = BORDER_UNKNOWN;
if (dx1 <= OFFSET) {
rectangleLongitude = WEST_BORDER;
} else if (dx2 <= OFFSET) {
rectangleLongitude = EAST_BORDER;
} else if (x >= x1 && x < x2) {
rectangleLongitude = NO_LONGITUDE_BORDER;
}
rectangleLatitude = BORDER_UNKNOWN;
if (dy1 <= OFFSET) {
rectangleLatitude = NORTH_BORDER;
} else if (dy2 <= OFFSET) {
rectangleLatitude = SOUTH_BORDER;
} else if (y > y1 && y < y2) {
rectangleLatitude = NO_LATITUDE_BORDER;
}
}
@Override
public void mouseDragged(MouseEvent event) {
rectangleIsCurrentlyDrawn = true;
double dx = event.getX() - point.getX();
double dy = point.getY() - event.getY();
double xOfUpdatedRectangle = selectionRectangle.getX();
double yOfUpdatedRectangle = selectionRectangle.getY();
double widthOfUpdatedRectangle = selectionRectangle.getWidth();
double heightOfUpdatedRectangle = selectionRectangle.getHeight();
if (rectangleLongitude == NO_LONGITUDE_BORDER && rectangleLatitude == NO_LATITUDE_BORDER) {
xOfUpdatedRectangle = selectionRectangle.getX() + dx;
yOfUpdatedRectangle = selectionRectangle.getY() - dy;
}
if (rectangleLongitude == WEST_BORDER) {
xOfUpdatedRectangle += dx;
widthOfUpdatedRectangle -= dx;
} else if (rectangleLongitude == EAST_BORDER) {
widthOfUpdatedRectangle += dx;
}
if (rectangleLatitude == NORTH_BORDER) {
yOfUpdatedRectangle -= dy;
heightOfUpdatedRectangle += dy;
} else if (rectangleLatitude == SOUTH_BORDER) {
heightOfUpdatedRectangle -= dy;
}
if (widthOfUpdatedRectangle > 2 && heightOfUpdatedRectangle > 2 &&
!(selectionRectangle.getX() == xOfUpdatedRectangle
&& selectionRectangle.getY() == yOfUpdatedRectangle
&& selectionRectangle.getWidth() == widthOfUpdatedRectangle
&& selectionRectangle.getHeight() == heightOfUpdatedRectangle)) {
setMovableRectangleInImageCoordinates(xOfUpdatedRectangle, yOfUpdatedRectangle,
widthOfUpdatedRectangle, heightOfUpdatedRectangle);
Shape newFigureShape = getViewToModelTransform(event).createTransformedShape(movableRectangle);
final Rectangle2D modelRectangle = newFigureShape.getBounds2D();
adaptToModelRectangle(modelRectangle);
}
rectangleIsCurrentlyDrawn = false;
}
@Override
public void mouseReleased(MouseEvent event) {
selectionRectangle.setRect(movableRectangle);
}
private void reset() {
selectionRectangle.setRect(defaultRectangle);
movableRectangle.setRect(defaultRectangle);
cursorChanger.updateRectanglesForDragCursor();
updateProperties(defaultShape.getBounds2D());
updateFigure(defaultShape.getBounds2D());
worldMapPane.zoomAll();
}
private void updateProperties(Rectangle2D modelRectangle) {
try {
PropertySet propertySet = bindingContext.getPropertySet();
propertySet.getProperty(NORTH_BOUND).setValue(modelRectangle.getMaxY());
propertySet.getProperty(SOUTH_BOUND).setValue(modelRectangle.getMinY());
propertySet.getProperty(WEST_BOUND).setValue(modelRectangle.getMinX());
propertySet.getProperty(EAST_BOUND).setValue(modelRectangle.getMaxX());
} catch (ValidationException e) {
// should never come here
throw new IllegalStateException(e);
}
}
private void updateFigure(Rectangle2D modelRectangle) {
DefaultFigureEditor figureEditor = getFigureEditor();
DefaultFigureStyle defaultFigureStyle = createFigureStyle((DefaultFigureStyle) figureEditor.getDefaultPolygonStyle());
final ShapeFigure polygonFigure = figureEditor.getFigureFactory().createPolygonFigure(modelRectangle, defaultFigureStyle);
if (polygonFigure != null) {
figureEditor.getFigureCollection().removeAllFigures();
figureEditor.getFigureCollection().addFigure(polygonFigure);
}
}
private void setMovableRectangleInImageCoordinates(double x, double y, double width, double height) {
movableRectangle.setRect(x, y, width, height);
}
private void adaptToModelRectangle(Rectangle2D modelRectangle) {
correctBoundsIfNecessary(modelRectangle);
if (modelRectangle.getWidth() != 0 && modelRectangle.getHeight() != 0 &&
!modelRectangle.equals(getFirstFigure().getBounds())) {
updateFigure(modelRectangle);
updateProperties(modelRectangle);
}
}
private class BoundsChangeListener implements PropertyChangeListener {
private final String property;
private BoundsChangeListener(String property) {
this.property = property;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (rectangleIsCurrentlyDrawn) {
return;
}
final PropertySet propertySet = bindingContext.getPropertySet();
final Object westValue = propertySet.getProperty(WEST_BOUND).getValue();
final Object southValue = propertySet.getProperty(SOUTH_BOUND).getValue();
final Object eastValue = propertySet.getProperty(EAST_BOUND).getValue();
final Object northValue = propertySet.getProperty(NORTH_BOUND).getValue();
if (westValue == null || southValue == null || eastValue == null || northValue == null) {
return;
}
final Rectangle2D modelRectangle = getFirstFigure().getBounds();
double x = (property.equals(WEST_BOUND) ?
Double.parseDouble(westValue.toString()) :
modelRectangle.getX());
double y = (property.equals(SOUTH_BOUND) ?
Double.parseDouble(southValue.toString()) :
modelRectangle.getY());
double width = (property.equals(EAST_BOUND) || property.equals(WEST_BOUND) ?
Double.parseDouble(eastValue.toString()) - x :
modelRectangle.getWidth());
double height = (property.equals(NORTH_BOUND) || property.equals(SOUTH_BOUND) ?
Double.parseDouble(northValue.toString()) - y :
modelRectangle.getHeight());
modelRectangle.setRect(x, y, width, height);
adaptToModelRectangle(modelRectangle);
updateRectangles();
}
}
}
private class RegionSelectionDecoratingPanSupport extends WorldMapPane.DefaultPanSupport {
private Point p0;
private RegionSelectionDecoratingPanSupport(LayerCanvas layerCanvas) {
super(layerCanvas);
}
@Override
public void panStarted(MouseEvent event) {
super.panStarted(event);
p0 = event.getPoint();
final Rectangle2D.Double intersectionRectangle = createIntersectionRectangle();
if (intersectionRectangle.contains(event.getPoint())) {
regionSelectionInteractor.mousePressed(event);
}
}
@Override
public void performPan(MouseEvent event) {
final Rectangle2D.Double intersectionRectangle = createIntersectionRectangle();
if (intersectionRectangle.contains(p0)) {
regionSelectionInteractor.mouseDragged(event);
} else {
super.performPan(event);
}
}
@Override
public void panStopped(MouseEvent event) {
super.panStopped(event);
updateRectangles();
}
private Rectangle2D.Double createIntersectionRectangle() {
return new Rectangle2D.Double(selectionRectangle.getX() - OFFSET,
selectionRectangle.getY() - OFFSET,
selectionRectangle.getWidth() + 2 * OFFSET,
selectionRectangle.getHeight() + 2 * OFFSET);
}
}
private class ResetAction extends AbstractAction {
private ResetAction() {
putValue(LARGE_ICON_KEY, UIUtils.loadImageIcon("icons/Undo24.gif"));
}
@Override
public void actionPerformed(ActionEvent e) {
if (isEnabled()) {
regionSelectionInteractor.reset();
}
}
}
}