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.Insets; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.FocusEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import javax.swing.JComponent; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.Point; import com.revolsys.swing.Icons; import com.revolsys.swing.SwingUtil; import com.revolsys.swing.map.MapPanel; import com.revolsys.swing.map.Viewport2D; import com.revolsys.swing.preferences.PreferencesDialog; import com.revolsys.util.Booleans; import com.revolsys.util.OS; public class ZoomOverlay extends AbstractOverlay { private static final String PREFERENCE_WHEEL_FORWARDS_ZOOM_IN = "wheelForwardsZoomIn"; private static final String PREFERENCE_PATH = "/com/revolsys/gis/zoom"; public static final String ACTION_PAN = "pan"; public static final String ACTION_ZOOM = "zoom"; public static final String ACTION_ZOOM_BOX = "zoomBox"; private static final Cursor CURSOR_PAN = new Cursor(Cursor.HAND_CURSOR); private static final Cursor CURSOR_ZOOM_BOX = Icons.getCursor("cursor_zoom_box", 9, 9); private static final long serialVersionUID = 1L; private static final Color TRANS_BG = new Color(0, 0, 0, 30); public static final BasicStroke ZOOM_BOX_STROKE = new BasicStroke(2, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 2, new float[] { 6, 6 }, 0f); static { PreferencesDialog.get().addPreference("Zoom", "com.revolsys.gis", PREFERENCE_PATH, PREFERENCE_WHEEL_FORWARDS_ZOOM_IN, DataTypes.BOOLEAN, true); } private int panButton; private BufferedImage panImage; private int panX1 = -1; private int panX2 = -1; private int panY1 = -1; private int panY2 = -1; private int zoomBoxX1 = -1; private int zoomBoxX2; private int zoomBoxY1; private int zoomBoxY2; public ZoomOverlay(final MapPanel map) { super(map); addOverlayAction(ACTION_ZOOM_BOX, CURSOR_ZOOM_BOX); addOverlayAction(ACTION_PAN, CURSOR_PAN); } protected void cancel() { panClear(); zoomBoxClear(); repaint(); } @Override public void focusLost(final FocusEvent e) { cancel(); } public boolean isWheelForwardsZoomIn() { final Object wheelForwardsZoomIn = OS.getPreference("com.revolsys.gis", PREFERENCE_PATH, PREFERENCE_WHEEL_FORWARDS_ZOOM_IN); return !Booleans.isFalse(wheelForwardsZoomIn); } @Override public void keyPressed(final KeyEvent event) { final int keyCode = event.getKeyCode(); if (keyCode == KeyEvent.VK_ESCAPE) { cancel(); } else if (keyCode == KeyEvent.VK_SHIFT) { if (isMouseInMap()) { setOverlayAction(ACTION_ZOOM_BOX); } } } @Override public void keyReleased(final KeyEvent event) { if (!event.isShiftDown() && this.zoomBoxX1 == -1) { zoomBoxClear(); } } @Override public void keyTyped(final KeyEvent event) { } @Override public void mouseClicked(final MouseEvent event) { if (canOverrideOverlayAction(ACTION_ZOOM)) { final int button = event.getButton(); // Double click if (event.getClickCount() == 2) { final int x = event.getX(); final int y = event.getY(); int numSteps = 0; if (button == MouseEvent.BUTTON1 && !hasOverlayAction() || button == MouseEvent.BUTTON2) { // Left or middle button, zoom in numSteps = -1; } else if (button == MouseEvent.BUTTON3) { // Right mouse button, zoom out numSteps = 1; } if (numSteps != 0) { final Viewport2D viewport = getViewport(); final Point mapPoint = viewport.toModelPoint(x, y); final MapPanel map = getMap(); map.zoom(mapPoint, numSteps); event.consume(); } } } } @Override public void mouseDragged(final MouseEvent event) { if (zoomBoxDrag(event)) { } else if (panDrag(event)) { } } @Override public void mouseEntered(final MouseEvent e) { } @Override public void mouseExited(final MouseEvent e) { if (this.zoomBoxX1 == -1) { zoomBoxClear(); } } @Override public void mouseMoved(final MouseEvent event) { if (zoomBoxMove(event)) { } } @Override public void mousePressed(final MouseEvent event) { if (zoomBoxStart(event)) { } else if (panStart(event, false)) { } } @Override public void mouseReleased(final MouseEvent event) { if (panFinish(event)) { } else if (zoomBoxFinish(event)) { } } @Override public void mouseWheelMoved(final MouseWheelEvent event) { if (canOverrideOverlayAction(ACTION_ZOOM)) { int numSteps = 1; if (event.getUnitsToScroll() < 0) { numSteps = -1; } if (SwingUtil.isScrollReversed()) { numSteps = -numSteps; } if (!isWheelForwardsZoomIn()) { numSteps = -numSteps; } final int x = event.getX(); final int y = event.getY(); final Viewport2D viewport = getViewport(); final Point mapPoint = viewport.toModelPoint(x, y); final MapPanel map = getMap(); map.zoom(mapPoint, numSteps); event.consume(); } } @Override protected void paintComponent(final Viewport2D viewport, final Graphics2D graphics) { if (this.zoomBoxX1 != -1) { graphics.setColor(Color.DARK_GRAY); graphics.setStroke(ZOOM_BOX_STROKE); final int boxX = Math.min(this.zoomBoxX1, this.zoomBoxX2); final int boxY = Math.min(this.zoomBoxY1, this.zoomBoxY2); final int width = Math.abs(this.zoomBoxX2 - this.zoomBoxX1); final int height = Math.abs(this.zoomBoxY2 - this.zoomBoxY1); graphics.drawRect(boxX, boxY, width, height); graphics.setPaint(TRANS_BG); graphics.fillRect(boxX, boxY, width, height); } if (this.panX1 != -1 && this.panImage != null) { final int dx = this.panX2 - this.panX1; final int dy = this.panY2 - this.panY1; final int width = viewport.getViewWidthPixels(); final int height = viewport.getViewHeightPixels(); graphics.setColor(Color.WHITE); graphics.fillRect(0, 0, width, height); graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); final AffineTransform transform = AffineTransform.getTranslateInstance(dx, dy); graphics.drawRenderedImage(this.panImage, transform); } } protected void panClear() { this.panImage = null; this.panX1 = -1; this.panY1 = -1; this.panX2 = -1; this.panY2 = -1; clearOverlayAction(ACTION_PAN); final MapPanel map = getMap(); map.clearVisibleOverlay(this); } public boolean panDrag(final MouseEvent event) { if (panStart(event, true)) { this.panX2 = event.getX(); this.panY2 = event.getY(); repaint(); return true; } return false; } public boolean panFinish(final MouseEvent event) { if (event.getButton() == this.panButton) { if (clearOverlayAction(ACTION_PAN) && this.panX1 != -1) { this.panImage = null; if (this.panX1 != this.panX2 || this.panY1 != this.panY2) { final java.awt.Point point = event.getPoint(); final Viewport2D viewport = getViewport(); final Point fromPoint = viewport.toModelPoint(this.panX1, this.panY1); final Point toPoint = viewport.toModelPoint(point); final double deltaX = fromPoint.getX() - toPoint.getX(); final double deltaY = fromPoint.getY() - toPoint.getY(); final BoundingBox boundingBox = viewport.getBoundingBox(); final BoundingBox newBoundingBox = boundingBox.move(deltaX, deltaY); final MapPanel map = getMap(); map.setBoundingBox(newBoundingBox); } panClear(); repaint(); return true; } } return false; } public boolean panStart(final MouseEvent event, final boolean drag) { if (this.panX1 == -1) { boolean pan = false; final int button = event.getButton(); if (button == MouseEvent.BUTTON2) { pan = true; this.panButton = MouseEvent.BUTTON2; } else if (!drag && button == MouseEvent.BUTTON1 && !hasOverlayAction()) { if (event.getModifiersEx() == InputEvent.BUTTON1_DOWN_MASK) { pan = true; this.panButton = MouseEvent.BUTTON1; } } if (pan) { if (setOverlayAction(ACTION_PAN)) { final Viewport2D viewport = getViewport(); final int width = viewport.getViewWidthPixels(); final int height = viewport.getViewHeightPixels(); if (width > 0 && height > 0) { final JComponent parent = (JComponent)getParent(); this.panImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); final Graphics2D graphics = (Graphics2D)this.panImage.getGraphics(); try { final Insets insets = parent.getInsets(); graphics.translate(-insets.left, -insets.top); graphics.setColor(Color.WHITE); graphics.fillRect(insets.left, insets.top, width, height); parent.paintComponents(graphics); } finally { graphics.dispose(); } this.panX1 = this.panX2 = event.getX(); this.panY1 = this.panY2 = event.getY(); final MapPanel map = getMap(); map.setVisibleOverlay(this); } return true; } } return false; } else { return true; } } protected void zoomBoxClear() { this.zoomBoxX1 = -1; this.zoomBoxY1 = -1; this.zoomBoxX2 = -1; this.zoomBoxY2 = -1; clearOverlayAction(ACTION_ZOOM_BOX); } protected boolean zoomBoxDrag(final MouseEvent event) { if (isOverlayAction(ACTION_ZOOM_BOX)) { this.zoomBoxX2 = event.getX(); this.zoomBoxY2 = event.getY(); repaint(); return true; } else { return false; } } protected boolean zoomBoxFinish(final MouseEvent event) { if (event.getButton() == MouseEvent.BUTTON1 && clearOverlayAction(ACTION_ZOOM_BOX)) { final Viewport2D viewport = getViewport(); // Convert first point to envelope top left in map coords. final int minX = Math.min(this.zoomBoxX1, this.zoomBoxX2); final int minY = Math.min(this.zoomBoxY1, this.zoomBoxY2); final Point topLeft = viewport.toModelPoint(minX, minY); // Convert second point to envelope bottom right in map coords. final int maxX = Math.max(this.zoomBoxX1, this.zoomBoxX2); final int maxY = Math.max(this.zoomBoxY1, this.zoomBoxY2); final Point bottomRight = viewport.toModelPoint(maxX, maxY); final MapPanel map = getMap(); final GeometryFactory geometryFactory = map.getGeometryFactory(); final BoundingBox boundingBox = geometryFactory.newBoundingBox(topLeft.getX(), topLeft.getY(), bottomRight.getX(), bottomRight.getY()); if (boundingBox.isEmpty()) { Toolkit.getDefaultToolkit().beep(); } else { map.setBoundingBox(boundingBox); } zoomBoxClear(); if (SwingUtil.isShiftDown(event) && isMouseInMap()) { setOverlayAction(ACTION_ZOOM_BOX); } return true; } return false; } protected boolean zoomBoxMove(final MouseEvent event) { final int modifiers = event.getModifiersEx(); if (modifiers == InputEvent.SHIFT_DOWN_MASK) { if (setOverlayAction(ACTION_ZOOM_BOX)) { return true; } } return false; } protected boolean zoomBoxStart(final MouseEvent event) { if (isOverlayAction(ACTION_ZOOM_BOX) && event.getButton() == MouseEvent.BUTTON1) { this.zoomBoxX1 = this.zoomBoxX2 = event.getX(); this.zoomBoxY1 = this.zoomBoxY2 = event.getY(); return true; } return false; } }