// License: GPL. Copyright (C) 2012 Russell Edwards
package org.openstreetmap.josm.plugins.gpsblam;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.AWTEvent;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Arc2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.mapmode.MapMode;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
class GPSBlamMode extends MapMode implements LayerChangeListener, MouseWheelListener, AWTEventListener {
Point pointPressed;
private Point oldP2;
int radius;
MouseWheelListener[] mapViewWheelListeners;
GPSBlamLayer currentBlamLayer;
GPSBlamMode(String name, String desc) {
super(name, "gpsblam_mode.png", desc, Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
radius = 10;
}
@Override
public void enterMode() {
super.enterMode();
Main.map.mapView.addMouseListener(this);
Main.map.mapView.addMouseMotionListener(this);
Main.map.mapView.addMouseWheelListener(this);
try {
Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
} catch (SecurityException ex) {
Main.error(ex);
}
getLayerManager().addLayerChangeListener(this);
}
@Override
public void eventDispatched(AWTEvent e) {
if (e instanceof KeyEvent) {
KeyEvent ke = (KeyEvent)e;
if (ke.getKeyCode()==KeyEvent.VK_UP)
changeRadiusBy(1);
else if (ke.getKeyCode()==KeyEvent.VK_DOWN)
changeRadiusBy(-1);
}
}
@Override
public void exitMode() {
super.exitMode();
getLayerManager().removeLayerChangeListener(this);
Main.map.mapView.removeMouseListener(this);
Main.map.mapView.removeMouseMotionListener(this);
Main.map.mapView.removeMouseWheelListener(this);
Toolkit.getDefaultToolkit().removeAWTEventListener(this);
}
@Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
pointPressed = new Point(e.getPoint());
// gain exclusive use of mouse wheel for now
mapViewWheelListeners = Main.map.mapView.getMouseWheelListeners();
for (MouseWheelListener l : mapViewWheelListeners) {
if (l != this)
Main.map.mapView.removeMouseWheelListener(l);
}
paintBox(pointPressed, radius);
}
}
@Override
public void mouseDragged(MouseEvent e) {
if ( (e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) == InputEvent.BUTTON1_DOWN_MASK) {
//if button1 is hold, draw the line
paintBox(e.getPoint(), radius);
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
// give mapView back its mouse wheel
for (MouseWheelListener l : mapViewWheelListeners) {
if (l != this)
Main.map.mapView.addMouseWheelListener(l);
}
xorDrawBox(pointPressed, oldP2, radius); // clear box
GPSBlamInputData inputData = new GPSBlamInputData(pointPressed, e.getPoint(), radius);
if (!inputData.isEmpty()) {
if(currentBlamLayer == null) {
currentBlamLayer = new GPSBlamLayer(tr("GPSBlam"));
getLayerManager().addLayer(currentBlamLayer);
}
currentBlamLayer.addBlamMarker(new GPSBlamMarker(inputData));
Main.map.mapView.repaint();
}
pointPressed = oldP2 = null;
}
private void changeRadiusBy(int delta) {
if (pointPressed != null) {
int newRadius = radius + delta;
if (newRadius < 1)
newRadius = 1;
paintBox(oldP2, newRadius);
radius = newRadius;
}
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if ( (e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) == InputEvent.BUTTON1_DOWN_MASK) {
changeRadiusBy(-e.getWheelRotation());
}
}
private void xorDrawBox(Point p1, Point p2, int radius){
if (Main.map != null) {
Graphics2D g = (Graphics2D) Main.map.mapView.getGraphics();
g.setXORMode(Color.BLACK);
g.setColor(Color.WHITE);
// AA+XOR broken in some versions of Java
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g.setStroke(new BasicStroke(2.0f));
if (p2==null)
p2 = p1;
double length = Math.sqrt((p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y));
if (length > 0) {
double dirX = (p2.x-p1.x)/length, dirY = (p2.y-p1.y)/length; // unit direction vector from p1.x,p1.y to p2.x, p2.y
double perpdirX = dirY, perpdirY = -dirX; // unit vector 90deg CW from direction vector
double angle = Math.atan2(-perpdirY, perpdirX); // polar angle of perpdir
double ofsX = radius * perpdirX, ofsY = radius * perpdirY; // radius vector, 90deg CW from dir vector
Path2D path = new Path2D.Double();
path.append(new Line2D.Double(p1.x+ofsX, p1.y+ofsY,p2.x+ofsX, p2.y+ofsY), false);
path.append(new Arc2D.Double(p2.x-radius, p2.y-radius,radius*2, radius*2,
Math.toDegrees(angle), -180, Arc2D.OPEN), true);
path.append(new Line2D.Double(p2.x-ofsX, p2.y-ofsY,p1.x-ofsX, p1.y-ofsY), true);
path.append(new Arc2D.Double(p1.x-radius, p1.y-radius,radius*2, radius*2,
Math.toDegrees(angle)-180, -180, Arc2D.OPEN), true);
path.closePath();
g.draw(path);
} else {
try {
g.setXORMode(Color.BLACK);
g.setColor(Color.WHITE);
g.drawOval(Math.round(p2.x-radius), Math.round(p2.y-radius),
Math.round(radius*2), Math.round(radius*2));
} catch (InternalError e) {
// Robustness against Java bug https://bugs.openjdk.java.net/browse/JDK-8041647
Main.error(e);
Main.error("Java bug JDK-8041647 occured. To avoid this bug, please consult https://bugs.openjdk.java.net/browse/JDK-8041647."
+" If the bug is fixed, please update your Java Runtime Environment.");
}
}
}
}
private void paintBox(Point p2, int newRadius) {
if (Main.map != null) {
if (oldP2 != null) {
xorDrawBox(pointPressed, oldP2, radius); // clear old box
}
xorDrawBox(pointPressed, p2, newRadius); // draw new box
oldP2 = p2;
}
}
@Override
public void layerOrderChanged(LayerOrderChangeEvent e) {
// Do nothing
}
@Override
public void layerAdded(LayerAddEvent e) {
// Do nothing
}
@Override
public void layerRemoving(LayerRemoveEvent e) {
if (e.getRemovedLayer() instanceof GPSBlamLayer) {
currentBlamLayer = null;
if (Main.map.mapMode instanceof GPSBlamMode)
Main.map.selectSelectTool(false);
}
}
}