package com.revolsys.swing.map.overlay;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.revolsys.awt.WebColors;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.Geometry;
import com.revolsys.geometry.model.GeometryFactory;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.vertex.Vertex;
import com.revolsys.io.BaseCloseable;
import com.revolsys.raster.GeoreferencedImage;
import com.revolsys.record.Record;
import com.revolsys.swing.Icons;
import com.revolsys.swing.SwingUtil;
import com.revolsys.swing.map.ImageViewport;
import com.revolsys.swing.map.MapPanel;
import com.revolsys.swing.map.Viewport2D;
import com.revolsys.swing.map.layer.AbstractLayer;
import com.revolsys.swing.map.layer.Layer;
import com.revolsys.swing.map.layer.LayerGroup;
import com.revolsys.swing.map.layer.LayerRenderer;
import com.revolsys.swing.map.layer.Project;
import com.revolsys.swing.map.layer.raster.GeoreferencedImageLayerRenderer;
import com.revolsys.swing.map.layer.record.AbstractRecordLayer;
import com.revolsys.swing.map.layer.record.LayerRecord;
import com.revolsys.swing.map.layer.record.renderer.AbstractRecordLayerRenderer;
import com.revolsys.swing.parallel.Invoke;
import com.revolsys.util.Property;
import com.revolsys.value.ThreadBooleanValue;
public class SelectRecordsOverlay extends AbstractOverlay {
public static final String ACTION_SELECT_RECORDS = "Select Records";
protected static final BasicStroke BOX_STROKE = new BasicStroke(2, BasicStroke.CAP_SQUARE,
BasicStroke.JOIN_MITER, 2, new float[] {
6, 6
}, 0f);
private static final Color COLOR_BOX = WebColors.Green;
private static final Color COLOR_BOX_TRANSPARENT = WebColors.newAlpha(COLOR_BOX, 127);
private static final Cursor CURSOR_SELECT_BOX = Icons.getCursor("cursor_select_box", 8, 7);
private static final Cursor CURSOR_SELECT_BOX_ADD = Icons.getCursor("cursor_select_box_add", 8,
7);
private static final Cursor CURSOR_SELECT_BOX_DELETE = Icons.getCursor("cursor_select_box_delete",
8, 7);
private static final Set<String> REDRAW_PROPERTY_NAMES = new HashSet<>(
Arrays.asList("refresh", "viewBoundingBox", "unitsPerPixel", "scale"));
private static final Set<String> REDRAW_REPAINT_PROPERTY_NAMES = new HashSet<>(Arrays.asList(
"layers", "selectable", "visible", "editable", AbstractRecordLayer.RECORDS_CHANGED,
"hasSelectedRecords", "hasHighlightedRecords", "minimumScale", "maximumScale",
AbstractRecordLayer.RECORD_UPDATED, AbstractRecordLayer.RECORDS_DELETED));
private static final VertexStyleRenderer CLOSE_VERTEX_STYLE_RENDERER = new VertexStyleRenderer(
WebColors.Green);
private static final long serialVersionUID = 1L;
private final SelectedRecordsRenderer selectRenderer = new SelectedRecordsRenderer(WebColors.Lime,
50);
private final SelectedRecordsVertexRenderer selectVertexRenderer = new SelectedRecordsVertexRenderer(
WebColors.Lime, false);
private final SelectedRecordsRenderer highlightRenderer = new SelectedRecordsRenderer(
WebColors.Yellow, 50);
private final SelectedRecordsVertexRenderer highlightVertexRenderer = new SelectedRecordsVertexRenderer(
WebColors.Yellow, false);
private int selectBoxButton;
private double selectBoxX1 = -1;
private double selectBoxX2 = -1;
private double selectBoxY1 = -1;
private double selectBoxY2 = -1;
private final BackgroundRefreshResource<GeoreferencedImage> imageSelected = new BackgroundRefreshResource<>(
"Selected Records Overlay", this::refreshImageSelected);
private final ThreadBooleanValue selectingRecords = new ThreadBooleanValue(false);
public SelectRecordsOverlay(final MapPanel mapPanel) {
super(mapPanel);
addOverlayAction( //
ACTION_SELECT_RECORDS, //
CURSOR_SELECT_BOX, //
ZoomOverlay.ACTION_PAN, //
ZoomOverlay.ACTION_ZOOM //
);
this.imageSelected.addPropertyChangeListener(this);
}
public void addSelectedRecords(final BoundingBox boundingBox) {
final LayerGroup project = getProject();
addSelectedRecords(project, boundingBox);
final LayerRendererOverlay overlay = getMap().getLayerOverlay();
overlay.redraw();
}
private void addSelectedRecords(final LayerGroup group, final BoundingBox boundingBox) {
final double scale = getViewport().getScale();
final List<Layer> layers = group.getLayers();
Collections.reverse(layers);
for (final Layer layer : layers) {
if (layer instanceof LayerGroup) {
final LayerGroup childGroup = (LayerGroup)layer;
addSelectedRecords(childGroup, boundingBox);
} else if (layer instanceof AbstractRecordLayer) {
final AbstractRecordLayer recordLayer = (AbstractRecordLayer)layer;
if (recordLayer.isSelectable(scale)) {
recordLayer.addSelectedRecords(boundingBox);
}
}
}
}
protected void cancel() {
selectBoxClear();
repaint();
}
protected void doSelectRecords(final InputEvent event, final BoundingBox boundingBox) {
if (SwingUtil.isShiftDown(event)) {
Invoke.background("Select records", () -> addSelectedRecords(boundingBox));
} else if (SwingUtil.isAltDown(event)) {
Invoke.background("Unselect records", () -> unSelectRecords(boundingBox));
} else {
Invoke.background("Select records", () -> selectRecords(boundingBox));
}
}
@Override
public void focusLost(final FocusEvent e) {
cancel();
}
protected boolean isSelectable(final AbstractLayer recordLayer) {
return recordLayer.isSelectable();
}
public boolean isSelectEvent(final MouseEvent event) {
if (event.getButton() == MouseEvent.BUTTON1) {
final boolean keyPress = SwingUtil.isControlOrMetaDown(event);
return keyPress;
}
return false;
}
@Override
public void keyPressed(final KeyEvent event) {
final int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.VK_ESCAPE) {
cancel();
} else if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_META) {
if (isMouseInMap() && !hasOverlayAction()) {
setSelectCursor(event);
}
} else if (isOverlayAction(ACTION_SELECT_RECORDS)) {
if (keyCode == KeyEvent.VK_SHIFT) {
setSelectCursor(event);
} else if (keyCode == KeyEvent.VK_ALT) {
setSelectCursor(event);
}
}
}
@Override
public void keyReleased(final KeyEvent event) {
final int keyCode = event.getKeyCode();
if (isOverlayAction(ACTION_SELECT_RECORDS)) {
if (keyCode == KeyEvent.VK_SHIFT) {
setSelectCursor(event);
} else if (keyCode == KeyEvent.VK_ALT) {
setSelectCursor(event);
} else if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_META) {
setSelectCursor(event);
}
}
}
@Override
public void mouseDragged(final MouseEvent event) {
if (selectBoxDrag(event)) {
}
}
@Override
public void mouseExited(final MouseEvent e) {
if (this.selectBoxX1 == -1) {
selectBoxClear();
}
}
@Override
public void mouseMoved(final MouseEvent event) {
if (canOverrideOverlayAction(ACTION_SELECT_RECORDS)) {
setSelectCursor(event);
}
}
@Override
public void mousePressed(final MouseEvent event) {
if (selectBoxStart(event)) {
}
}
@Override
public void mouseReleased(final MouseEvent event) {
if (selectBoxFinish(event)) {
}
}
@Override
public void paintComponent(final Viewport2D viewport, final Graphics2D graphics) {
final GeoreferencedImage imageSelected = this.imageSelected.getResource();
if (imageSelected != null) {
GeoreferencedImageLayerRenderer.render(viewport, graphics, imageSelected, false);
}
final GeometryFactory viewportGeometryFactory = getViewportGeometryFactory2d();
final MapPanel map = getMap();
final List<LayerRecord> closeSelectedRecords = map.getCloseSelectedRecords();
if (!closeSelectedRecords.isEmpty()) {
try (
BaseCloseable transformCloseable = viewport.setUseModelCoordinates(graphics, true)) {
for (final LayerRecord record : closeSelectedRecords) {
final Geometry geometry = record.getGeometry();
if (record.isHighlighted()) {
this.highlightVertexRenderer.paintSelected(viewport, graphics, viewportGeometryFactory,
geometry);
} else {
this.selectVertexRenderer.paintSelected(viewport, graphics, viewportGeometryFactory,
geometry);
}
}
}
}
final List<CloseLocation> closeSelectedLocations = map.getCloseSelectedLocations();
if (Property.hasValue(closeSelectedLocations)) {
for (final CloseLocation location : closeSelectedLocations) {
final Vertex vertex = location.getVertex();
CLOSE_VERTEX_STYLE_RENDERER.paintSelected(viewport, graphics, viewportGeometryFactory,
vertex);
}
}
paintSelectBox(graphics);
}
protected void paintSelectBox(final Graphics2D graphics2d) {
if (this.selectBoxX1 != -1) {
final Viewport2D viewport = getViewport();
final Point2D from = viewport.toViewPoint(this.selectBoxX1, this.selectBoxY1);
final Point2D to = viewport.toViewPoint(this.selectBoxX2, this.selectBoxY2);
final double x1 = from.getX();
final double x2 = to.getX();
final int x = (int)Math.min(x1, x2);
final int width = (int)Math.abs(x1 - x2);
final double y1 = from.getY();
final double y2 = to.getY();
final int y = (int)Math.min(y1, y2);
final int height = (int)Math.abs(y1 - y2);
graphics2d.setColor(COLOR_BOX);
graphics2d.setStroke(BOX_STROKE);
graphics2d.drawRect(x, y, width, height);
graphics2d.setPaint(COLOR_BOX_TRANSPARENT);
graphics2d.fillRect(x, y, width, height);
}
}
@Override
public void propertyChange(final PropertyChangeEvent event) {
final Object source = event.getSource();
final String propertyName = event.getPropertyName();
if (this.selectingRecords != null && !this.selectingRecords.isTrue()) {
if (source == this.imageSelected) {
repaint();
} else if (source instanceof Record || source instanceof LayerRenderer) {
redraw();
} else if (REDRAW_PROPERTY_NAMES.contains(propertyName)) {
redraw();
} else if (REDRAW_REPAINT_PROPERTY_NAMES.contains(propertyName)
|| source instanceof AbstractRecordLayer && propertyName.startsWith("record")) {
redrawAndRepaint();
}
}
}
public void redraw() {
this.imageSelected.refresh();
}
public void redrawAndRepaint() {
redraw();
repaint();
}
private void refreshImageRenderer(final ImageViewport viewport, final LayerGroup layerGroup) {
for (final Layer layer : layerGroup.getLayers()) {
if (layer instanceof LayerGroup) {
final LayerGroup childGroup = (LayerGroup)layer;
refreshImageRenderer(viewport, childGroup);
} else if (layer instanceof AbstractRecordLayer) {
final AbstractRecordLayer recordLayer = (AbstractRecordLayer)layer;
final AbstractRecordLayerRenderer layerRenderer = layer.getRenderer();
if (recordLayer.isSelectable()) {
final List<LayerRecord> selectedRecords = recordLayer.getSelectedRecords();
for (final LayerRecord record : selectedRecords) {
if (record != null && recordLayer.isVisible(record)) {
if (!recordLayer.isDeleted(record)) {
layerRenderer.renderSelectedRecord(viewport, recordLayer, record);
}
}
}
}
}
}
}
private GeoreferencedImage refreshImageSelected() {
final Viewport2D viewport = getViewport();
if (viewport != null) {
final int width = viewport.getViewWidthPixels();
final int height = viewport.getViewHeightPixels();
if (width > 0 && height > 0) {
try (
final ImageViewport imageViewport = new ImageViewport(viewport,
BufferedImage.TYPE_INT_ARGB_PRE);
BaseCloseable transformCloseable = imageViewport.setUseModelCoordinates(true);) {
final Graphics2D graphics = imageViewport.getGraphics();
final Project project = getProject();
refreshImageRenderer(imageViewport, project);
refreshImageSelectedAndHighlighted(imageViewport, graphics, project);
return imageViewport.getGeoreferencedImage();
}
}
}
return null;
}
private void refreshImageSelectedAndHighlighted(final ImageViewport viewport,
final Graphics2D graphics, final LayerGroup layerGroup) {
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
final GeometryFactory viewportGeometryFactory = getViewportGeometryFactory2d();
final List<Geometry> highlightedGeometries = new ArrayList<>();
for (final Layer layer : layerGroup.getLayers()) {
if (layer instanceof LayerGroup) {
final LayerGroup childGroup = (LayerGroup)layer;
refreshImageSelectedAndHighlighted(viewport, graphics, childGroup);
} else if (layer instanceof AbstractRecordLayer) {
final AbstractRecordLayer recordLayer = (AbstractRecordLayer)layer;
if (recordLayer.isSelectable()) {
final List<LayerRecord> selectedRecords = recordLayer.getSelectedRecords();
for (final LayerRecord record : selectedRecords) {
if (record != null && recordLayer.isVisible(record)) {
if (!recordLayer.isDeleted(record)) {
final Geometry geometry = record.getGeometry();
if (recordLayer.isHighlighted(record)) {
highlightedGeometries.add(geometry);
} else {
this.selectRenderer.paintSelected(viewport, graphics, viewportGeometryFactory,
geometry);
}
}
}
}
}
}
}
for (final Geometry geometry : highlightedGeometries) {
this.highlightRenderer.paintSelected(viewport, graphics, viewportGeometryFactory, geometry);
}
}
private void selectBoxClear() {
clearOverlayAction(ACTION_SELECT_RECORDS);
this.selectBoxX1 = -1;
this.selectBoxY1 = -1;
this.selectBoxX2 = -1;
this.selectBoxY2 = -1;
}
private boolean selectBoxDrag(final MouseEvent event) {
if (isOverlayAction(ACTION_SELECT_RECORDS) && this.selectBoxX1 != -1) {
final Point point = getPoint(event);
this.selectBoxX2 = point.getX();
this.selectBoxY2 = point.getY();
setSelectCursor(event);
repaint();
return true;
}
return false;
}
private boolean selectBoxFinish(final MouseEvent event) {
if (event.getButton() == this.selectBoxButton && this.selectBoxX1 != -1) {
if (clearOverlayAction(ACTION_SELECT_RECORDS)) {
final MapPanel map = getMap();
final GeometryFactory geometryFactory = map.getGeometryFactory();
BoundingBox boundingBox = geometryFactory.newBoundingBox(this.selectBoxX1, this.selectBoxY1,
this.selectBoxX2, this.selectBoxY2);
final Viewport2D viewport = getViewport();
final double minSize = viewport.getModelUnitsPerViewUnit() * 10;
final double width = boundingBox.getWidth();
double deltaX = 0;
if (width < minSize) {
deltaX = (minSize - width) / 2;
}
final double height = boundingBox.getWidth();
double deltaY = 0;
if (height < minSize) {
deltaY = (minSize - height) / 2;
}
boundingBox = boundingBox.expand(deltaX, deltaY);
if (!boundingBox.isEmpty()) {
doSelectRecords(event, boundingBox);
}
selectBoxClear();
if (isMouseInMap()) {
setSelectCursor(event);
}
event.consume();
repaint();
return true;
}
}
return false;
}
private boolean selectBoxStart(final MouseEvent event) {
if (isOverlayAction(ACTION_SELECT_RECORDS) && SwingUtil.isLeftButtonOnly(event)) {
this.selectBoxButton = event.getButton();
final Point point = getPoint(event);
this.selectBoxX1 = this.selectBoxX2 = point.getX();
this.selectBoxY1 = this.selectBoxY2 = point.getY();
return true;
}
return false;
}
public void selectRecords(final BoundingBox boundingBox) {
try (
BaseCloseable closeable = this.selectingRecords.closeable(true)) {
final LayerGroup project = getProject();
selectRecords(project, boundingBox);
final LayerRendererOverlay overlay = getMap().getLayerOverlay();
overlay.redraw();
redrawAndRepaint();
}
}
private void selectRecords(final LayerGroup group, final BoundingBox boundingBox) {
final double scale = getViewport().getScale();
final List<Layer> layers = group.getLayers();
Collections.reverse(layers);
for (final Layer layer : layers) {
if (layer instanceof LayerGroup) {
final LayerGroup childGroup = (LayerGroup)layer;
selectRecords(childGroup, boundingBox);
} else if (layer instanceof AbstractRecordLayer) {
final AbstractRecordLayer recordLayer = (AbstractRecordLayer)layer;
if (recordLayer.isSelectable(scale)) {
recordLayer.setSelectedRecords(boundingBox);
} else {
recordLayer.clearSelectedRecords();
}
}
}
}
public void setHighlightColors(final Color color) {
this.highlightRenderer.setStyleColor(color);
this.highlightVertexRenderer.setStyleColor(color);
}
public void setSelectColors(final Color color) {
this.selectRenderer.setStyleColor(color);
this.selectVertexRenderer.setStyleColor(color);
}
protected void setSelectCursor(final InputEvent event) {
Cursor cursor = null;
if (event != null) {
final boolean selectBox = SwingUtil.isControlOrMetaDown(event) || this.selectBoxX1 != -1;
if (SwingUtil.isShiftDown(event)) {
if (selectBox) {
cursor = CURSOR_SELECT_BOX_ADD;
}
} else if (SwingUtil.isAltDown(event)) {
if (selectBox) {
cursor = CURSOR_SELECT_BOX_DELETE;
}
} else if (SwingUtil.isControlOrMetaDown(event)) {
if (selectBox || !hasOverlayAction()) {
cursor = CURSOR_SELECT_BOX;
}
} else if (this.selectBoxX1 != -1) {
cursor = CURSOR_SELECT_BOX;
}
}
if (cursor == null) {
clearOverlayAction(ACTION_SELECT_RECORDS);
} else {
setOverlayAction(ACTION_SELECT_RECORDS);
setMapCursor(cursor);
}
}
public void unSelectRecords(final BoundingBox boundingBox) {
final LayerGroup project = getProject();
unSelectRecords(project, boundingBox);
final LayerRendererOverlay overlay = getMap().getLayerOverlay();
overlay.redraw();
}
private void unSelectRecords(final LayerGroup group, final BoundingBox boundingBox) {
final double scale = getViewport().getScale();
final List<Layer> layers = group.getLayers();
Collections.reverse(layers);
for (final Layer layer : layers) {
if (layer instanceof LayerGroup) {
final LayerGroup childGroup = (LayerGroup)layer;
unSelectRecords(childGroup, boundingBox);
} else if (layer instanceof AbstractRecordLayer) {
final AbstractRecordLayer recordLayer = (AbstractRecordLayer)layer;
if (recordLayer.isSelectable(scale)) {
recordLayer.unSelectRecords(boundingBox);
}
}
}
}
}