diff --git a/.classpath b/.classpath
index 11208d5..b5edc13 100644
--- a/.classpath
+++ b/.classpath
@@ -1,7 +1,7 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/.project b/.project
index 4800fe3..5502783 100644
--- a/.project
+++ b/.project
@@ -1,23 +1,23 @@
-
-
- JOSM-improve-way
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- net.sf.eclipsecs.core.CheckstyleBuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
- net.sf.eclipsecs.core.CheckstyleNature
-
-
+
+
+ JOSM-improve-way
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ net.sf.eclipsecs.core.CheckstyleBuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ net.sf.eclipsecs.core.CheckstyleNature
+
+
diff --git a/build.xml b/build.xml
index fd9e908..ad38f78 100644
--- a/build.xml
+++ b/build.xml
@@ -1,21 +1,21 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/org/openstreetmap/josm/core/patch/ImproveWayAccuracyAction.java b/src/org/openstreetmap/josm/core/patch/ImproveWayAccuracyAction.java
new file mode 100644
index 0000000..dfe66b3
--- /dev/null
+++ b/src/org/openstreetmap/josm/core/patch/ImproveWayAccuracyAction.java
@@ -0,0 +1,747 @@
+/**
+ * This file is copy of same named class
+ * from package org.openstreetmap.josm.actions.mapmode
+ * --
+ * Serves as base class for ImproveWay plugin
+ * until this file is merged in core.
+ */
+
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.core.patch;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+import static org.openstreetmap.josm.tools.I18n.trn;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.actions.mapmode.MapMode;
+import org.openstreetmap.josm.command.AddCommand;
+import org.openstreetmap.josm.command.ChangeNodesCommand;
+import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.DeleteCommand;
+import org.openstreetmap.josm.command.MoveCommand;
+import org.openstreetmap.josm.command.SequenceCommand;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.UndoRedoHandler;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.DataSelectionListener;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
+import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
+import org.openstreetmap.josm.data.osm.event.DataSetListener;
+import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
+import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
+import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
+import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
+import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
+import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
+import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
+import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
+import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
+import org.openstreetmap.josm.data.preferences.CachingProperty;
+import org.openstreetmap.josm.data.preferences.IntegerProperty;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
+import org.openstreetmap.josm.data.preferences.StrokeProperty;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.draw.MapViewPath;
+import org.openstreetmap.josm.gui.draw.SymbolShape;
+import org.openstreetmap.josm.gui.layer.AbstractMapViewPaintable;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.util.ModifierExListener;
+import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.Pair;
+import org.openstreetmap.josm.tools.Shortcut;
+
+/**
+ * A special map mode that is optimized for improving way geometry.
+ * (by efficiently moving, adding and deleting way-nodes)
+ *
+ * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011
+ */
+public class ImproveWayAccuracyAction extends MapMode implements DataSelectionListener, DataSetListener, ModifierExListener {
+
+ protected static final String CROSSHAIR = /* ICON(cursor/)*/ "crosshair";
+
+ protected enum State {
+ SELECTING, IMPROVING
+ }
+
+ protected State state;
+
+ protected MapView mv;
+
+ protected static final long serialVersionUID = 42L;
+
+ protected transient Way targetWay;
+ protected transient Node candidateNode;
+ protected transient WaySegment candidateSegment;
+
+ protected Point mousePos;
+ protected boolean dragging;
+
+ protected Node endpoint1 = null;
+ protected Node endpoint2 = null;
+
+ protected final Cursor cursorSelect = ImageProvider.getCursor(/* ICON(cursor/)*/ "normal", /* ICON(cursor/modifier/)*/ "mode");
+ protected final Cursor cursorSelectHover = ImageProvider.getCursor(/* ICON(cursor/)*/ "hand", /* ICON(cursor/modifier/)*/ "mode");
+ protected final Cursor cursorImprove = ImageProvider.getCursor(CROSSHAIR, null);
+ protected final Cursor cursorImproveAdd = ImageProvider.getCursor(CROSSHAIR, /* ICON(cursor/modifier/)*/ "addnode");
+ protected final Cursor cursorImproveDelete = ImageProvider.getCursor(CROSSHAIR, /* ICON(cursor/modifier/)*/ "delete_node");
+ protected final Cursor cursorImproveAddLock = ImageProvider.getCursor(CROSSHAIR, /* ICON(cursor/modifier/)*/ "add_node_lock");
+ protected final Cursor cursorImproveLock = ImageProvider.getCursor(CROSSHAIR, /* ICON(cursor/modifier/)*/ "lock");
+
+ protected Color guideColor;
+
+ protected static final CachingProperty SELECT_TARGET_WAY_STROKE
+ = new StrokeProperty("improvewayaccuracy.stroke.select-target", "2").cached();
+ protected static final CachingProperty MOVE_NODE_STROKE
+ = new StrokeProperty("improvewayaccuracy.stroke.move-node", "1 6").cached();
+ protected static final CachingProperty MOVE_NODE_INTERSECTING_STROKE
+ = new StrokeProperty("improvewayaccuracy.stroke.move-node-intersecting", "1 2 6").cached();
+ protected static final CachingProperty ADD_NODE_STROKE
+ = new StrokeProperty("improvewayaccuracy.stroke.add-node", "1").cached();
+ protected static final CachingProperty DELETE_NODE_STROKE
+ = new StrokeProperty("improvewayaccuracy.stroke.delete-node", "1").cached();
+ protected static final CachingProperty DOT_SIZE
+ = new IntegerProperty("improvewayaccuracy.dot-size", 6).cached();
+
+ protected boolean selectionChangedBlocked;
+
+ protected String oldModeHelpText;
+
+ protected final transient AbstractMapViewPaintable temporaryLayer = new AbstractMapViewPaintable() {
+ @Override
+ public void paint(Graphics2D g, MapView mv, Bounds bbox) {
+ ImproveWayAccuracyAction.this.paint(g, mv, bbox);
+ }
+ };
+
+ /**
+ * Constructs a new {@code ImproveWayAccuracyAction}.
+ * @since 11713
+ */
+ public ImproveWayAccuracyAction() {
+ super(tr("Improve Way Accuracy"), "improvewayaccuracy",
+ tr("Improve Way Accuracy mode"),
+ Shortcut.registerShortcut("mapmode:ImproveWayAccuracy",
+ tr("Mode: {0}", tr("Improve Way Accuracy")),
+ KeyEvent.VK_W, Shortcut.DIRECT), Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+
+ readPreferences();
+ }
+
+ protected ImproveWayAccuracyAction(String name, String iconName, String tooltip, Shortcut shortcut, Cursor cursor) {
+ super(name, iconName, tooltip, shortcut, cursor);
+ }
+
+ // -------------------------------------------------------------------------
+ // Mode methods
+ // -------------------------------------------------------------------------
+ @Override
+ public void enterMode() {
+ if (!isEnabled()) {
+ return;
+ }
+ super.enterMode();
+ readPreferences();
+
+ MapFrame map = MainApplication.getMap();
+ mv = map.mapView;
+ mousePos = null;
+ oldModeHelpText = "";
+
+ if (getLayerManager().getEditDataSet() == null) {
+ return;
+ }
+
+ updateStateByCurrentSelection();
+
+ map.mapView.addMouseListener(this);
+ map.mapView.addMouseMotionListener(this);
+ map.mapView.addTemporaryLayer(temporaryLayer);
+ SelectionEventManager.getInstance().addSelectionListener(this);
+ DatasetEventManager.getInstance().addDatasetListener(this, FireMode.IMMEDIATELY);
+
+ map.keyDetector.addModifierExListener(this);
+ }
+
+ @Override
+ protected void readPreferences() {
+ guideColor = new NamedColorProperty(marktr("improve way accuracy helper line"), Color.RED).get();
+ }
+
+ @Override
+ public void exitMode() {
+ super.exitMode();
+
+ MapFrame map = MainApplication.getMap();
+ map.mapView.removeMouseListener(this);
+ map.mapView.removeMouseMotionListener(this);
+ map.mapView.removeTemporaryLayer(temporaryLayer);
+ SelectionEventManager.getInstance().removeSelectionListener(this);
+ DatasetEventManager.getInstance().removeDatasetListener(this);
+
+ map.keyDetector.removeModifierExListener(this);
+ temporaryLayer.invalidate();
+ targetWay = null;
+ candidateNode = null;
+ candidateSegment = null;
+ }
+
+ @Override
+ protected void updateStatusLine() {
+ String newModeHelpText = getModeHelpText();
+ if (!newModeHelpText.equals(oldModeHelpText)) {
+ oldModeHelpText = newModeHelpText;
+ MapFrame map = MainApplication.getMap();
+ map.statusLine.setHelpText(newModeHelpText);
+ map.statusLine.repaint();
+ }
+ }
+
+ @Override
+ public String getModeHelpText() {
+ if (state == State.SELECTING) {
+ if (targetWay != null) {
+ return tr("Click on the way to start improving its shape.");
+ } else {
+ return tr("Select a way that you want to make more accurate.");
+ }
+ } else {
+ if (ctrl) {
+ return tr("Click to add a new node. Release Ctrl to move existing nodes or hold Alt to delete.");
+ } else if (alt) {
+ return tr("Click to delete the highlighted node. Release Alt to move existing nodes or hold Ctrl to add new nodes.");
+ } else {
+ return tr("Click to move the highlighted node. Hold Ctrl to add new nodes, or Alt to delete.");
+ }
+ }
+ }
+
+ @Override
+ public boolean layerIsSupported(Layer l) {
+ return isEditableDataLayer(l);
+ }
+
+ @Override
+ protected void updateEnabledState() {
+ setEnabled(getLayerManager().getEditLayer() != null);
+ }
+
+ // -------------------------------------------------------------------------
+ // MapViewPaintable methods
+ // -------------------------------------------------------------------------
+ /**
+ * Redraws temporary layer. Highlights targetWay in select mode. Draws
+ * preview lines in improve mode and highlights the candidateNode
+ * @param g The graphics
+ * @param mv The map view
+ * @param bbox The bounding box
+ */
+ public void paint(Graphics2D g, MapView mv, Bounds bbox) {
+ if (mousePos == null || (candidateNode != null && candidateNode.getDataSet() == null)) {
+ return;
+ }
+
+ g.setColor(guideColor);
+
+ if (state == State.SELECTING && targetWay != null) {
+ // Highlighting the targetWay in Selecting state
+ // Non-native highlighting is used, because sometimes highlighted
+ // segments are covered with others, which is bad.
+ BasicStroke stroke = SELECT_TARGET_WAY_STROKE.get();
+ g.setStroke(stroke);
+
+ List nodes = targetWay.getNodes();
+
+ g.draw(new MapViewPath(mv).append(nodes, false).computeClippedLine(stroke));
+
+ } else if (state == State.IMPROVING) {
+ // Drawing preview lines and highlighting the node
+ // that is going to be moved.
+ // Non-native highlighting is used here as well.
+ MapViewPath b = new MapViewPath(mv);
+ findEndpoints(g);
+ drawPreviewLines(g, b);
+ highlightCandidateNode(g, mv, b);
+ }
+ }
+
+ protected void findEndpoints(Graphics2D g) {
+ endpoint1 = null;
+ endpoint2 = null;
+ if (ctrl && candidateSegment != null) {
+ g.setStroke(ADD_NODE_STROKE.get());
+ try {
+ endpoint1 = candidateSegment.getFirstNode();
+ endpoint2 = candidateSegment.getSecondNode();
+ } catch (ArrayIndexOutOfBoundsException e) {
+ Logging.error(e);
+ }
+ } else if (!alt && !ctrl && candidateNode != null) {
+ g.setStroke(MOVE_NODE_STROKE.get());
+ List> wpps = targetWay.getNodePairs(false);
+ for (Pair wpp : wpps) {
+ if (wpp.a == candidateNode) {
+ endpoint1 = wpp.b;
+ }
+ if (wpp.b == candidateNode) {
+ endpoint2 = wpp.a;
+ }
+ if (endpoint1 != null && endpoint2 != null) {
+ break;
+ }
+ }
+ } else if (alt && !ctrl && candidateNode != null) {
+ g.setStroke(DELETE_NODE_STROKE.get());
+ List nodes = targetWay.getNodes();
+ int index = nodes.indexOf(candidateNode);
+
+ // Only draw line if node is not first and/or last
+ if (index > 0 && index < (nodes.size() - 1)) {
+ endpoint1 = nodes.get(index - 1);
+ endpoint2 = nodes.get(index + 1);
+ } else if (targetWay.isClosed()) {
+ endpoint1 = targetWay.getNode(1);
+ endpoint2 = targetWay.getNode(nodes.size() - 2);
+ }
+ // TODO: indicate what part that will be deleted? (for end nodes)
+ }
+ }
+
+ protected void drawPreviewLines(Graphics2D g, MapViewPath b) {
+ if (alt && !ctrl) {
+ // In delete mode
+ if (endpoint1 != null && endpoint2 != null) {
+ b.moveTo(endpoint1);
+ b.lineTo(endpoint2);
+ }
+ } else {
+ // In add or move mode
+ if (endpoint1 != null) {
+ b.moveTo(mousePos.x, mousePos.y);
+ b.lineTo(endpoint1);
+ }
+ if (endpoint2 != null) {
+ b.moveTo(mousePos.x, mousePos.y);
+ b.lineTo(endpoint2);
+ }
+ }
+ g.draw(b.computeClippedLine(g.getStroke()));
+ }
+
+ protected void highlightCandidateNode(Graphics2D g, MapView mv, MapViewPath b) {
+ if (candidateNode != null) {
+ g.fill(new MapViewPath(mv).shapeAround(candidateNode, SymbolShape.SQUARE, DOT_SIZE.get()));
+ }
+
+ if (!alt && !ctrl && candidateNode != null) {
+ b.reset();
+ drawIntersectingWayHelperLines(b);
+ g.setStroke(MOVE_NODE_INTERSECTING_STROKE.get());
+ g.draw(b.computeClippedLine(g.getStroke()));
+ }
+ }
+
+ protected void drawIntersectingWayHelperLines(MapViewPath b) {
+ for (final OsmPrimitive referrer : candidateNode.getReferrers()) {
+ if (!(referrer instanceof Way) || targetWay.equals(referrer)) {
+ continue;
+ }
+ final List nodes = ((Way) referrer).getNodes();
+ for (int i = 0; i < nodes.size(); i++) {
+ if (!candidateNode.equals(nodes.get(i))) {
+ continue;
+ }
+ if (i > 0) {
+ b.moveTo(mousePos.x, mousePos.y);
+ b.lineTo(nodes.get(i - 1));
+ }
+ if (i < nodes.size() - 1) {
+ b.moveTo(mousePos.x, mousePos.y);
+ b.lineTo(nodes.get(i + 1));
+ }
+ }
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Event handlers
+ // -------------------------------------------------------------------------
+ @Override
+ public void modifiersExChanged(int modifiers) {
+ if (!MainApplication.isDisplayingMapView() || !MainApplication.getMap().mapView.isActiveLayerDrawable()) {
+ return;
+ }
+ updateKeyModifiersEx(modifiers);
+ updateCursorDependentObjectsIfNeeded();
+ updateCursor();
+ updateStatusLine();
+ temporaryLayer.invalidate();
+ }
+
+ @Override
+ public void selectionChanged(SelectionChangeEvent event) {
+ if (selectionChangedBlocked) {
+ return;
+ }
+ updateStateByCurrentSelection();
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ dragging = true;
+ mouseMoved(e);
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ if (!isEnabled()) {
+ return;
+ }
+
+ updateMousePosition(e);
+ updateKeyModifiers(e);
+ updateCursorDependentObjectsIfNeeded();
+ updateCursor();
+ updateStatusLine();
+ temporaryLayer.invalidate();
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ dragging = false;
+ if (!isEnabled() || e.getButton() != MouseEvent.BUTTON1) {
+ return;
+ }
+
+ DataSet ds = getLayerManager().getEditDataSet();
+ updateKeyModifiers(e);
+ updateMousePosition(e);
+
+ if (state == State.SELECTING) {
+ if (targetWay != null) {
+ ds.setSelected(targetWay.getPrimitiveId());
+ updateStateByCurrentSelection();
+ }
+ } else if (state == State.IMPROVING) {
+ // Checking if the new coordinate is outside of the world
+ if (new Node(mv.getEastNorth(mousePos.x, mousePos.y)).isOutSideWorld()) {
+ JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
+ tr("Cannot add a node outside of the world."),
+ tr("Warning"), JOptionPane.WARNING_MESSAGE);
+ return;
+ }
+
+ if (ctrl && !alt && candidateSegment != null) {
+ // Add a new node to the highlighted segment.
+ Collection virtualSegments = new LinkedList<>();
+
+ // Check if other ways have the same segment.
+ // We have to make sure that we add the new node to all of them.
+ Set commonParentWays = new HashSet<>(candidateSegment.getFirstNode().getParentWays());
+ commonParentWays.retainAll(candidateSegment.getSecondNode().getParentWays());
+ for (Way w : commonParentWays) {
+ for (int i = 0; i < w.getNodesCount() - 1; i++) {
+ WaySegment testWS = new WaySegment(w, i);
+ if (testWS.isSimilar(candidateSegment)) {
+ virtualSegments.add(testWS);
+ }
+ }
+ }
+
+ Collection virtualCmds = new LinkedList<>();
+ // Create the new node
+ Node virtualNode = new Node(mv.getEastNorth(mousePos.x, mousePos.y));
+ virtualCmds.add(new AddCommand(ds, virtualNode));
+
+ // Adding the node to all segments found
+ for (WaySegment virtualSegment : virtualSegments) {
+ Way w = virtualSegment.getWay();
+ List modNodes = w.getNodes();
+ modNodes.add(virtualSegment.getUpperIndex(), virtualNode);
+ virtualCmds.add(new ChangeNodesCommand(w, modNodes));
+ }
+
+ // Finishing the sequence command
+ String text = trn("Add a new node to way",
+ "Add a new node to {0} ways",
+ virtualSegments.size(), virtualSegments.size());
+
+ UndoRedoHandler.getInstance().add(new SequenceCommand(text, virtualCmds));
+
+ } else if (alt && !ctrl && candidateNode != null) {
+ // Deleting the highlighted node
+
+ //check to see if node is in use by more than one object
+ long referrersCount = candidateNode.referrers(OsmPrimitive.class).count();
+ long referrerWayCount = candidateNode.referrers(Way.class).count();
+ if (referrersCount != 1 || referrerWayCount != 1) {
+ // detach node from way
+ final List nodes = targetWay.getNodes();
+ nodes.remove(candidateNode);
+ if (nodes.size() < 2) {
+ final Command deleteCmd = DeleteCommand.delete(Collections.singleton(targetWay), true);
+ if (deleteCmd != null) {
+ UndoRedoHandler.getInstance().add(deleteCmd);
+ }
+ } else {
+ UndoRedoHandler.getInstance().add(new ChangeNodesCommand(targetWay, nodes));
+ }
+ } else if (candidateNode.isTagged()) {
+ JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
+ tr("Cannot delete node that has tags"),
+ tr("Error"), JOptionPane.ERROR_MESSAGE);
+ } else {
+ final Command deleteCmd = DeleteCommand.delete(Collections.singleton(candidateNode), true);
+ if (deleteCmd != null) {
+ UndoRedoHandler.getInstance().add(deleteCmd);
+ }
+ }
+
+ } else if (candidateNode != null) {
+ // Moving the highlighted node
+ EastNorth nodeEN = candidateNode.getEastNorth();
+ EastNorth cursorEN = mv.getEastNorth(mousePos.x, mousePos.y);
+
+ UndoRedoHandler.getInstance().add(
+ new MoveCommand(candidateNode, cursorEN.east() - nodeEN.east(), cursorEN.north() - nodeEN.north()));
+
+ // TODO the following line is commented out in this copy because checkCommandForLargeDistance method is package private
+ // SelectAction.checkCommandForLargeDistance(UndoRedoHandler.getInstance().getLastCommand());
+ }
+ }
+
+ mousePos = null;
+ updateCursor();
+ updateStatusLine();
+ temporaryLayer.invalidate();
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (!isEnabled()) {
+ return;
+ }
+
+ if (!dragging) {
+ mousePos = null;
+ }
+ temporaryLayer.invalidate();
+ }
+
+ // -------------------------------------------------------------------------
+ // Custom methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Sets mouse position based on mouse event;
+ * this method allows extending classes to override position
+ */
+ protected void updateMousePosition(MouseEvent e) {
+ mousePos = e.getPoint();
+ }
+
+ /**
+ * Sets new cursor depending on state, mouse position
+ */
+ protected void updateCursor() {
+ if (!isEnabled()) {
+ mv.setNewCursor(null, this);
+ return;
+ }
+
+ if (state == State.SELECTING) {
+ mv.setNewCursor(targetWay == null ? cursorSelect
+ : cursorSelectHover, this);
+ } else if (state == State.IMPROVING) {
+ if (alt && !ctrl) {
+ mv.setNewCursor(cursorImproveDelete, this);
+ } else if (shift || dragging) {
+ if (ctrl) {
+ mv.setNewCursor(cursorImproveAddLock, this);
+ } else {
+ mv.setNewCursor(cursorImproveLock, this);
+ }
+ } else if (ctrl && !alt) {
+ mv.setNewCursor(cursorImproveAdd, this);
+ } else {
+ mv.setNewCursor(cursorImprove, this);
+ }
+ }
+ }
+
+ /**
+ * Updates these objects under cursor: targetWay, candidateNode,
+ * candidateSegment
+ */
+ public void updateCursorDependentObjectsIfNeeded() {
+ if (state == State.IMPROVING && (shift || dragging)
+ && !(candidateNode == null && candidateSegment == null)) {
+ return;
+ }
+
+ if (mousePos == null) {
+ candidateNode = null;
+ candidateSegment = null;
+ return;
+ }
+
+ if (state == State.SELECTING) {
+ targetWay = ImproveWayAccuracyHelper.findWay(mv, mousePos);
+ } else if (state == State.IMPROVING) {
+ if (ctrl && !alt) {
+ candidateSegment = ImproveWayAccuracyHelper.findCandidateSegment(mv,
+ targetWay, mousePos);
+ candidateNode = null;
+ } else {
+ candidateNode = ImproveWayAccuracyHelper.findCandidateNode(mv,
+ targetWay, mousePos);
+ candidateSegment = null;
+ }
+ }
+ }
+
+ /**
+ * Switches to Selecting state
+ */
+ public void startSelecting() {
+ state = State.SELECTING;
+
+ targetWay = null;
+
+ temporaryLayer.invalidate();
+ updateStatusLine();
+ }
+
+ /**
+ * Switches to Improving state
+ *
+ * @param targetWay Way that is going to be improved
+ */
+ public void startImproving(Way targetWay) {
+ state = State.IMPROVING;
+
+ DataSet ds = getLayerManager().getEditDataSet();
+ Collection currentSelection = ds.getSelected();
+ if (currentSelection.size() != 1
+ || !currentSelection.iterator().next().equals(targetWay)) {
+ selectionChangedBlocked = true;
+ ds.clearSelection();
+ ds.setSelected(targetWay.getPrimitiveId());
+ selectionChangedBlocked = false;
+ }
+
+ this.targetWay = targetWay;
+ this.candidateNode = null;
+ this.candidateSegment = null;
+
+ temporaryLayer.invalidate();
+ updateStatusLine();
+ }
+
+ /**
+ * Updates the state according to the current selection. Goes to Improve
+ * state if a single way or node is selected. Extracts a way by a node in
+ * the second case.
+ */
+ protected void updateStateByCurrentSelection() {
+ final List nodeList = new ArrayList<>();
+ final List wayList = new ArrayList<>();
+ final DataSet ds = getLayerManager().getEditDataSet();
+ if (ds != null) {
+ final Collection sel = ds.getSelected();
+
+ // Collecting nodes and ways from the selection
+ for (OsmPrimitive p : sel) {
+ if (p instanceof Way) {
+ wayList.add((Way) p);
+ }
+ if (p instanceof Node) {
+ nodeList.add((Node) p);
+ }
+ }
+
+ if (wayList.size() == 1) {
+ // Starting improving the single selected way
+ startImproving(wayList.get(0));
+ return;
+ } else if (nodeList.size() == 1) {
+ // Starting improving the only way of the single selected node
+ List r = nodeList.get(0).getReferrers();
+ if (r.size() == 1 && (r.get(0) instanceof Way)) {
+ startImproving((Way) r.get(0));
+ return;
+ }
+ }
+ }
+
+ // Starting selecting by default
+ startSelecting();
+ }
+
+ @Override
+ public void primitivesRemoved(PrimitivesRemovedEvent event) {
+ if (event.getPrimitives().contains(candidateNode) || event.getPrimitives().contains(targetWay)) {
+ updateCursorDependentObjectsIfNeeded();
+ }
+ }
+
+ @Override
+ public void primitivesAdded(PrimitivesAddedEvent event) {
+ // Do nothing
+ }
+
+ @Override
+ public void tagsChanged(TagsChangedEvent event) {
+ // Do nothing
+ }
+
+ @Override
+ public void nodeMoved(NodeMovedEvent event) {
+ // Do nothing
+ }
+
+ @Override
+ public void wayNodesChanged(WayNodesChangedEvent event) {
+ // Do nothing
+ }
+
+ @Override
+ public void relationMembersChanged(RelationMembersChangedEvent event) {
+ // Do nothing
+ }
+
+ @Override
+ public void otherDatasetChange(AbstractDatasetChangedEvent event) {
+ // Do nothing
+ }
+
+ @Override
+ public void dataChanged(DataChangedEvent event) {
+ // Do nothing
+ }
+}
diff --git a/src/org/openstreetmap/josm/plugins/improveway/ImproveWayAccuracyHelper.java b/src/org/openstreetmap/josm/core/patch/ImproveWayAccuracyHelper.java
similarity index 79%
rename from src/org/openstreetmap/josm/plugins/improveway/ImproveWayAccuracyHelper.java
rename to src/org/openstreetmap/josm/core/patch/ImproveWayAccuracyHelper.java
index 8e86a06..6db3c11 100644
--- a/src/org/openstreetmap/josm/plugins/improveway/ImproveWayAccuracyHelper.java
+++ b/src/org/openstreetmap/josm/core/patch/ImproveWayAccuracyHelper.java
@@ -1,177 +1,178 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.improveway;
-
-import java.awt.Point;
-import java.util.Collection;
-import java.util.List;
-
-import org.openstreetmap.josm.data.coor.EastNorth;
-import org.openstreetmap.josm.data.osm.IWaySegment;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.MapView;
-import org.openstreetmap.josm.tools.Geometry;
-import org.openstreetmap.josm.tools.Pair;
-
-/**
- * This static class contains functions used to find target way, node to move or
- * segment to divide.
- *
- * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011
- */
-final class ImproveWayAccuracyHelper {
-
- private ImproveWayAccuracyHelper() {
- // Hide default constructor for utils classes
- }
-
- /**
- * Finds the way to work on. If the mouse is on the node, extracts one of
- * the ways containing it. If the mouse is on the way, simply returns it.
- *
- * @param mv the current map view
- * @param p the cursor position
- * @return {@code Way} or {@code null} in case there is nothing under the cursor.
- */
- public static Way findWay(MapView mv, Point p) {
- if (mv == null || p == null) {
- return null;
- }
-
- Node node = mv.getNearestNode(p, OsmPrimitive::isSelectable);
- Way candidate = null;
-
- if (node != null) {
- final Collection candidates = node.getReferrers();
- for (OsmPrimitive refferer : candidates) {
- if (refferer instanceof Way) {
- candidate = (Way) refferer;
- break;
- }
- }
- if (candidate != null) {
- return candidate;
- }
- }
-
- return MainApplication.getMap().mapView.getNearestWay(p, OsmPrimitive::isSelectable);
- }
-
- /**
- * Returns the nearest node to cursor. All nodes that are behind segments
- * are neglected. This is to avoid way self-intersection after moving the
- * candidateNode to a new place.
- *
- * @param mv the current map view
- * @param w the way to check
- * @param p the cursor position
- * @return nearest node to cursor
- */
- public static Node findCandidateNode(MapView mv, Way w, Point p) {
- if (mv == null || w == null || p == null) {
- return null;
- }
-
- EastNorth pEN = mv.getEastNorth(p.x, p.y);
-
- Double bestDistance = Double.MAX_VALUE;
- Double currentDistance;
- List> wpps = w.getNodePairs(false);
-
- Node result = null;
-
- mainLoop:
- for (Node n : w.getNodes()) {
- EastNorth nEN = n.getEastNorth();
-
- if (nEN == null) {
- // Might happen if lat/lon for that point are not known.
- continue;
- }
-
- currentDistance = pEN.distance(nEN);
-
- if (currentDistance < bestDistance) {
- // Making sure this candidate is not behind any segment.
- for (Pair wpp : wpps) {
- if (!wpp.a.equals(n)
- && !wpp.b.equals(n)
- && Geometry.getSegmentSegmentIntersection(
- wpp.a.getEastNorth(), wpp.b.getEastNorth(),
- pEN, nEN) != null) {
- continue mainLoop;
- }
- }
- result = n;
- bestDistance = currentDistance;
- }
- }
-
- return result;
- }
-
- /**
- * Returns the nearest way segment to cursor. The distance to segment ab is
- * the length of altitude from p to ab (say, c) or the minimum distance from
- * p to a or b if c is out of ab.
- *
- * The priority is given to segments where c is in ab. Otherwise, a segment
- * with the largest angle apb is chosen.
- *
- * @param mv the current map view
- * @param w the way to check
- * @param p the cursor position
- * @return nearest way segment to cursor
- */
- public static IWaySegment findCandidateSegment(MapView mv, Way w, Point p) {
- if (mv == null || w == null || p == null) {
- return null;
- }
-
- EastNorth pEN = mv.getEastNorth(p.x, p.y);
-
- Double currentDistance;
- Double currentAngle;
- Double bestDistance = Double.MAX_VALUE;
- Double bestAngle = 0.0;
-
- int candidate = -1;
-
- List> wpps = w.getNodePairs(true);
-
- int i = -1;
- for (Pair wpp : wpps) {
- ++i;
-
- EastNorth a = wpp.a.getEastNorth();
- EastNorth b = wpp.b.getEastNorth();
-
- // Finding intersection of the segment with its altitude from p
- EastNorth altitudeIntersection = Geometry.closestPointToSegment(a, b, pEN);
- currentDistance = pEN.distance(altitudeIntersection);
-
- if (!altitudeIntersection.equals(a) && !altitudeIntersection.equals(b)) {
- // If the segment intersects with the altitude from p,
- // make an angle too big to let this candidate win any others
- // having the same distance.
- currentAngle = Double.MAX_VALUE;
- } else {
- // Otherwise measure the angle
- currentAngle = Math.abs(Geometry.getCornerAngle(a, pEN, b));
- }
-
- if (currentDistance < bestDistance
- || (currentAngle > bestAngle && currentDistance < bestDistance * 1.0001 /*
- * equality
- */)) {
- candidate = i;
- bestAngle = currentAngle;
- bestDistance = currentDistance;
- }
-
- }
- return candidate != -1 ? new IWaySegment<>(w, candidate) : null;
- }
-}
+/**
+ * This file is copy of same named class
+ * from package org.openstreetmap.josm.actions.mapmode
+ * --
+ * Serves as base class for ImproveWay plugin
+ * until this file is merged in core.
+ */
+
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.core.patch;
+
+import java.awt.Point;
+import java.util.List;
+import java.util.Optional;
+
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
+import org.openstreetmap.josm.data.osm.Way;
+import org.openstreetmap.josm.data.osm.WaySegment;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.tools.Geometry;
+import org.openstreetmap.josm.tools.Pair;
+
+/**
+ * This static class contains functions used to find target way, node to move or
+ * segment to divide.
+ *
+ * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011
+ */
+class ImproveWayAccuracyHelper {
+
+ protected ImproveWayAccuracyHelper() {
+ // Hide default constructor for utils classes
+ }
+
+ /**
+ * Finds the way to work on. If the mouse is on the node, extracts one of
+ * the ways containing it. If the mouse is on the way, simply returns it.
+ *
+ * @param mv the current map view
+ * @param p the cursor position
+ * @return {@code Way} or {@code null} in case there is nothing under the cursor.
+ */
+ public static Way findWay(MapView mv, Point p) {
+ if (mv == null || p == null) {
+ return null;
+ }
+
+ Node node = mv.getNearestNode(p, OsmPrimitive::isSelectable);
+
+ if (node != null) {
+ Optional candidate = node.referrers(Way.class).findFirst();
+ if (candidate.isPresent()) {
+ return candidate.get();
+ }
+ }
+
+ return MainApplication.getMap().mapView.getNearestWay(p, OsmPrimitive::isSelectable);
+ }
+
+ /**
+ * Returns the nearest node to cursor. All nodes that are “behind” segments
+ * are neglected. This is to avoid way self-intersection after moving the
+ * candidateNode to a new place.
+ *
+ * @param mv the current map view
+ * @param w the way to check
+ * @param p the cursor position
+ * @return nearest node to cursor
+ */
+ public static Node findCandidateNode(MapView mv, Way w, Point p) {
+ if (mv == null || w == null || p == null) {
+ return null;
+ }
+
+ EastNorth pEN = mv.getEastNorth(p.x, p.y);
+
+ double bestDistance = Double.MAX_VALUE;
+ double currentDistance;
+ List> wpps = w.getNodePairs(false);
+
+ Node result = null;
+
+ mainLoop:
+ for (Node n : w.getNodes()) {
+ EastNorth nEN = n.getEastNorth();
+
+ if (nEN == null) {
+ // Might happen if lat/lon for that point are not known.
+ continue;
+ }
+
+ currentDistance = pEN.distance(nEN);
+
+ if (currentDistance < bestDistance) {
+ // Making sure this candidate is not behind any segment.
+ for (Pair wpp : wpps) {
+ if (!wpp.a.equals(n)
+ && !wpp.b.equals(n)
+ && Geometry.getSegmentSegmentIntersection(
+ wpp.a.getEastNorth(), wpp.b.getEastNorth(),
+ pEN, nEN) != null) {
+ continue mainLoop;
+ }
+ }
+ result = n;
+ bestDistance = currentDistance;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the nearest way segment to cursor. The distance to segment ab is
+ * the length of altitude from p to ab (say, c) or the minimum distance from
+ * p to a or b if c is out of ab.
+ *
+ * The priority is given to segments where c is in ab. Otherwise, a segment
+ * with the largest angle apb is chosen.
+ *
+ * @param mv the current map view
+ * @param w the way to check
+ * @param p the cursor position
+ * @return nearest way segment to cursor
+ */
+ public static WaySegment findCandidateSegment(MapView mv, Way w, Point p) {
+ if (mv == null || w == null || p == null) {
+ return null;
+ }
+
+ EastNorth pEN = mv.getEastNorth(p.x, p.y);
+
+ double currentDistance;
+ double currentAngle;
+ double bestDistance = Double.MAX_VALUE;
+ double bestAngle = 0.0;
+
+ int candidate = -1;
+
+ List> wpps = w.getNodePairs(true);
+
+ int i = -1;
+ for (Pair wpp : wpps) {
+ ++i;
+
+ EastNorth a = wpp.a.getEastNorth();
+ EastNorth b = wpp.b.getEastNorth();
+
+ // Finding intersection of the segment with its altitude from p
+ EastNorth altitudeIntersection = Geometry.closestPointToSegment(a, b, pEN);
+ currentDistance = pEN.distance(altitudeIntersection);
+
+ if (!altitudeIntersection.equals(a) && !altitudeIntersection.equals(b)) {
+ // If the segment intersects with the altitude from p,
+ // make an angle too big to let this candidate win any others
+ // having the same distance.
+ currentAngle = Double.MAX_VALUE;
+ } else {
+ // Otherwise measure the angle
+ currentAngle = Math.abs(Geometry.getCornerAngle(a, pEN, b));
+ }
+
+ if (currentDistance < bestDistance
+ || (currentAngle > bestAngle && currentDistance < bestDistance * 1.0001 /*
+ * equality
+ */)) {
+ candidate = i;
+ bestAngle = currentAngle;
+ bestDistance = currentDistance;
+ }
+
+ }
+ return candidate != -1 ? new WaySegment(w, candidate) : null;
+ }
+}
diff --git a/src/org/openstreetmap/josm/plugins/improveway/ImproveWayAccuracyAction.java b/src/org/openstreetmap/josm/plugins/improveway/ImproveWayAccuracyAction.java
deleted file mode 100644
index 2b1b063..0000000
--- a/src/org/openstreetmap/josm/plugins/improveway/ImproveWayAccuracyAction.java
+++ /dev/null
@@ -1,1032 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.improveway;
-
-import static org.openstreetmap.josm.tools.I18n.marktr;
-import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trn;
-
-import java.awt.Color;
-import java.awt.Cursor;
-import java.awt.FontMetrics;
-import java.awt.Graphics2D;
-import java.awt.Point;
-import java.awt.RenderingHints;
-import java.awt.Stroke;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseEvent;
-import java.awt.geom.Arc2D;
-import java.awt.geom.Ellipse2D;
-import java.awt.geom.GeneralPath;
-import java.awt.geom.Line2D;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
-
-import javax.swing.JOptionPane;
-
-import org.openstreetmap.josm.actions.ExpertToggleAction;
-import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener;
-import org.openstreetmap.josm.actions.mapmode.MapMode;
-import org.openstreetmap.josm.command.AddCommand;
-import org.openstreetmap.josm.command.ChangeCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.DeleteCommand;
-import org.openstreetmap.josm.command.MoveCommand;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.UndoRedoHandler;
-import org.openstreetmap.josm.data.coor.EastNorth;
-import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.data.osm.DataSelectionListener;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.IWaySegment;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
-import org.openstreetmap.josm.data.preferences.NamedColorProperty;
-import org.openstreetmap.josm.data.projection.ProjectionRegistry;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.MapFrame;
-import org.openstreetmap.josm.gui.MapView;
-import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.gui.layer.MapViewPaintable;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.gui.util.KeyPressReleaseListener;
-import org.openstreetmap.josm.gui.util.ModifierExListener;
-import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
-import org.openstreetmap.josm.tools.Geometry;
-import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Pair;
-import org.openstreetmap.josm.tools.Shortcut;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011
- */
-public class ImproveWayAccuracyAction extends MapMode implements MapViewPaintable,
- DataSelectionListener, ModifierExListener, KeyPressReleaseListener,
- ExpertModeChangeListener {
-
- enum State {
- selecting, improving
- }
-
- private State state;
-
- private MapView mv;
-
- private static final long serialVersionUID = 42L;
-
- private transient Way targetWay;
- private transient Node candidateNode;
- private transient IWaySegment candidateSegment;
-
- private Point mousePos;
- private boolean dragging;
-
- private final Cursor cursorSelect;
- private final Cursor cursorSelectHover;
- private final Cursor cursorImprove;
- private final Cursor cursorImproveAdd;
- private final Cursor cursorImproveDelete;
- private final Cursor cursorImproveAddLock;
- private final Cursor cursorImproveLock;
-
- private Color guideColor;
- private Color turnColor;
- private Color distanceColor;
- private Color arcFillColor;
- private Color arcStrokeColor;
- private Color perpendicularLineColor;
- private Color equalAngleCircleColor;
-
- private transient Stroke selectTargetWayStroke;
- private transient Stroke moveNodeStroke;
- private transient Stroke moveNodeIntersectingStroke;
- private transient Stroke addNodeStroke;
- private transient Stroke deleteNodeStroke;
- private transient Stroke arcStroke;
- private transient Stroke perpendicularLineStroke;
- private transient Stroke equalAngleCircleStroke;
- private int dotSize;
-
- private boolean selectionChangedBlocked;
-
- protected String oldModeHelpText;
-
- private int arcRadiusPixels;
- private int perpendicularLengthPixels;
- private int turnTextDistance;
- private int distanceTextDistance;
- private int equalAngleCircleRadius;
- private long longKeypressTime;
-
- private boolean helpersEnabled = false;
- private boolean helpersUseOriginal = false;
- private final transient Shortcut helpersShortcut;
- private long keypressTime = 0;
- private boolean helpersEnabledBeforeKeypressed = false;
- private Timer longKeypressTimer;
- private boolean isExpert = false;
-
- private boolean mod4 = false; // Windows/Super/Meta key
-
- /**
- * Constructs a new {@code ImproveWayAccuracyAction}.
- */
- public ImproveWayAccuracyAction() {
- super(tr("Improve Way"), "improveway",
- tr("Improve Way mode"),
- Shortcut.registerShortcut("mapmode:ImproveWay",
- tr("Mode: {0}", tr("Improve Way")),
- KeyEvent.VK_W, Shortcut.DIRECT), Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
-
- helpersShortcut = Shortcut.registerShortcut("mapmode:enablewayaccuracyhelpers",
- tr("Mode: Enable way accuracy helpers"), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE);
-
- cursorSelect = ImageProvider.getCursor("normal", "mode");
- cursorSelectHover = ImageProvider.getCursor("hand", "mode");
- cursorImprove = ImageProvider.getCursor("crosshair", null);
- cursorImproveAdd = ImageProvider.getCursor("crosshair", "addnode");
- cursorImproveDelete = ImageProvider.getCursor("crosshair", "delete_node");
- cursorImproveAddLock = ImageProvider.getCursor("crosshair",
- "add_node_lock");
- cursorImproveLock = ImageProvider.getCursor("crosshair", "lock");
- ExpertToggleAction.addExpertModeChangeListener(this, true);
- readPreferences();
- }
-
- // -------------------------------------------------------------------------
- // Mode methods
- // -------------------------------------------------------------------------
- @Override
- public void enterMode() {
- if (!isEnabled()) {
- return;
- }
- super.enterMode();
-
- MapFrame map = MainApplication.getMap();
- mv = map.mapView;
- mousePos = null;
- oldModeHelpText = "";
-
- if (getLayerManager().getEditDataSet() == null) {
- return;
- }
-
- updateStateByCurrentSelection();
-
- map.keyDetector.addKeyListener(this);
- map.mapView.addMouseListener(this);
- map.mapView.addMouseMotionListener(this);
- map.mapView.addTemporaryLayer(this);
- SelectionEventManager.getInstance().addSelectionListener(this);
-
- map.keyDetector.addModifierExListener(this);
-
- if (!isExpert) return;
- helpersEnabled = false;
- keypressTime = 0;
- resetTimer();
- longKeypressTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- helpersEnabled = true;
- helpersUseOriginal = true;
- MainApplication.getLayerManager().invalidateEditLayer();
- }
- }, longKeypressTime);
- }
-
- @Override
- protected void readPreferences() {
- guideColor = new NamedColorProperty(marktr("improve way accuracy helper line"), Color.RED).get();
- turnColor = new NamedColorProperty(marktr("improve way accuracy helper turn angle text"), new Color(240, 240, 240, 200)).get();
- distanceColor = new NamedColorProperty(marktr("improve way accuracy helper distance text"), new Color(240, 240, 240, 120)).get();
- arcFillColor = new NamedColorProperty(marktr("improve way accuracy helper arc fill"), new Color(200, 200, 200, 50)).get();
- arcStrokeColor = new NamedColorProperty(marktr("improve way accuracy helper arc stroke"), new Color(240, 240, 240, 150)).get();
- perpendicularLineColor = new NamedColorProperty(marktr("improve way accuracy helper perpendicular line"),
- new Color(240, 240, 240, 150)).get();
- equalAngleCircleColor = new NamedColorProperty(marktr("improve way accuracy helper equal angle circle"),
- new Color(240, 240, 240, 150)).get();
-
- selectTargetWayStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.select-target", "2"));
- moveNodeStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.move-node", "1 6"));
- moveNodeIntersectingStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.move-node-intersecting", "1 2 6"));
- addNodeStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.add-node", "1"));
- deleteNodeStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.delete-node", "1"));
- arcStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.helper-arc", "1"));
- perpendicularLineStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.helper-perpendicular-line", "1 6"));
- equalAngleCircleStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.helper-eual-angle-circle", "1"));
-
- dotSize = Config.getPref().getInt("improvewayaccuracy.dot-size", 6);
- arcRadiusPixels = Config.getPref().getInt("improvewayaccuracy.helper-arc-radius", 200);
- perpendicularLengthPixels = Config.getPref().getInt("improvewayaccuracy.helper-perpendicular-line-length", 100);
- turnTextDistance = Config.getPref().getInt("improvewayaccuracy.helper-turn-text-distance", 15);
- distanceTextDistance = Config.getPref().getInt("improvewayaccuracy.helper-distance-text-distance", 15);
- equalAngleCircleRadius = Config.getPref().getInt("improvewayaccuracy.helper-equal-angle-circle-radius", 15);
- longKeypressTime = Config.getPref().getInt("improvewayaccuracy.long-keypress-time", 250);
- }
-
- @Override
- public void exitMode() {
- super.exitMode();
-
- MainApplication.getMap().keyDetector.removeKeyListener(this);
- MainApplication.getMap().mapView.removeMouseListener(this);
- MainApplication.getMap().mapView.removeMouseMotionListener(this);
- MainApplication.getMap().mapView.removeTemporaryLayer(this);
- SelectionEventManager.getInstance().removeSelectionListener(this);
-
- MainApplication.getMap().keyDetector.removeModifierExListener(this);
- MainApplication.getLayerManager().invalidateEditLayer();
- }
-
- @Override
- protected void updateStatusLine() {
- String newModeHelpText = getModeHelpText();
- if (!newModeHelpText.equals(oldModeHelpText)) {
- oldModeHelpText = newModeHelpText;
- MainApplication.getMap().statusLine.setHelpText(newModeHelpText);
- MainApplication.getMap().statusLine.repaint();
- }
- }
-
- @Override
- public String getModeHelpText() {
- if (state == State.selecting) {
- if (targetWay != null) {
- return tr("Click on the way to start improving its shape.");
- } else {
- return tr("Select a way that you want to make more accurate.");
- }
- } else {
- if (ctrl) {
- return tr("Click to add a new node. Release Ctrl to move existing nodes or hold Alt to delete.");
- } else if (alt) {
- return tr("Click to delete the highlighted node. Release Alt to move existing nodes or hold Ctrl to add new nodes.");
- } else {
- return tr("Click to move the highlighted node. Hold Ctrl to add new nodes, or Alt to delete.");
- }
- }
- }
-
- @Override
- public boolean layerIsSupported(Layer l) {
- return l instanceof OsmDataLayer;
- }
-
- @Override
- protected void updateEnabledState() {
- setEnabled(getLayerManager().getEditLayer() != null);
- }
-
- // -------------------------------------------------------------------------
- // MapViewPaintable methods
- // -------------------------------------------------------------------------
- /**
- * Redraws temporary layer. Highlights targetWay in select mode. Draws
- * preview lines in improve mode and highlights the candidateNode
- */
- @Override
- public void paint(Graphics2D g, MapView mv, Bounds bbox) {
-
- g.setColor(guideColor);
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-
- if (state == State.selecting && targetWay != null) {
- // Highlighting the targetWay in Selecting state
- // Non-native highlighting is used, because sometimes highlighted
- // segments are covered with others, which is bad.
- g.setStroke(selectTargetWayStroke);
-
- List nodes = targetWay.getNodes();
-
- GeneralPath b = new GeneralPath();
- Point p0 = mv.getPoint(nodes.get(0));
- Point pn;
- b.moveTo(p0.x, p0.y);
-
- for (Node n : nodes) {
- pn = mv.getPoint(n);
- b.lineTo(pn.x, pn.y);
- }
- if (targetWay.isClosed()) {
- b.lineTo(p0.x, p0.y);
- }
-
- g.draw(b);
-
- } else if (state == State.improving) {
- // Drawing preview lines and highlighting the node
- // that is going to be moved.
- // Non-native highlighting is used here as well.
-
- // Finding endpoints
- Point p1 = null, p2 = null;
- if (ctrl && candidateSegment != null) {
- g.setStroke(addNodeStroke);
- p1 = mv.getPoint(candidateSegment.getFirstNode());
- p2 = mv.getPoint(candidateSegment.getSecondNode());
- } else if (!(alt ^ ctrl) && candidateNode != null) {
- g.setStroke(moveNodeStroke);
- List> wpps = targetWay.getNodePairs(false);
- for (Pair wpp : wpps) {
- if (wpp.a == candidateNode) {
- p1 = mv.getPoint(wpp.b);
- }
- if (wpp.b == candidateNode) {
- p2 = mv.getPoint(wpp.a);
- }
- if (p1 != null && p2 != null) {
- break;
- }
- }
- } else if (alt && !ctrl && candidateNode != null) {
- g.setStroke(deleteNodeStroke);
- List nodes = targetWay.getNodes();
- int index = nodes.indexOf(candidateNode);
-
- // Only draw line if node is not first and/or last
- if (index > 0 && index < (nodes.size() - 1)) {
- p1 = mv.getPoint(nodes.get(index - 1));
- p2 = mv.getPoint(nodes.get(index + 1));
- }
- // TODO: indicate what part that will be deleted? (for end nodes)
- }
-
- EastNorth newPointEN = getNewPointEN();
- Point newPoint = mv.getPoint(newPointEN);
-
- // Drawing preview lines
- GeneralPath b = new GeneralPath();
- if (alt && !ctrl) {
- // In delete mode
- if (p1 != null && p2 != null) {
- b.moveTo(p1.x, p1.y);
- b.lineTo(p2.x, p2.y);
- }
- } else if (newPointEN != null && newPoint != null) {
- // In add or move mode
- if (p1 != null) {
- b.moveTo(newPoint.x, newPoint.y);
- b.lineTo(p1.x, p1.y);
- }
- if (p2 != null) {
- b.moveTo(newPoint.x, newPoint.y);
- b.lineTo(p2.x, p2.y);
- }
- }
- g.draw(b);
-
- // Highlighting candidateNode
- if (candidateNode != null) {
- Point p = mv.getPoint(candidateNode);
- g.setColor(guideColor);
- g.fillRect(p.x - dotSize/2, p.y - dotSize/2, dotSize, dotSize);
- }
-
- if (!alt && !ctrl && candidateNode != null) {
- b.reset();
- drawIntersectingWayHelperLines(mv, b, newPoint);
- g.setStroke(moveNodeIntersectingStroke);
- g.draw(b);
- }
-
- // Painting helpers visualizing turn angles and more
- if (!helpersEnabled) return;
-
- // Perpendicular line at half distance
- if (!(alt && !ctrl) && p1 != null && p2 != null) {
- Point half = new Point(
- (p1.x + p2.x)/2,
- (p1.y + p2.y)/2
- );
- double heading = Math.atan2(
- p2.y-p1.y,
- p2.x-p1.x
- ) + Math.PI/2;
- g.setStroke(perpendicularLineStroke);
- g.setColor(perpendicularLineColor);
- g.draw(new Line2D.Double(
- half.x + perpendicularLengthPixels * Math.cos(heading),
- half.y + perpendicularLengthPixels * Math.sin(heading),
- half.x - perpendicularLengthPixels * Math.cos(heading),
- half.y - perpendicularLengthPixels * Math.sin(heading)
- ));
- }
-
- // Pie with turn angle
- Node node;
- LatLon coor, lastcoor = null;
- Point point, lastpoint = null;
- double distance;
- double heading, lastheading = 0;
- double turn;
- Arc2D arc;
- double arcRadius;
- boolean candidateSegmentVisited = false;
- int nodeCounter = 0;
- int nodesCount = targetWay.getNodesCount();
- int endLoop = nodesCount;
- if (targetWay.isClosed()) endLoop++;
- for (int i = 0; i < endLoop; i++) {
- // when way is closed we visit second node again
- // to get turn for start/end node
- node = targetWay.getNode(i == nodesCount ? 1 : i);
- if (!helpersUseOriginal && newPointEN != null &&
- ctrl &&
- !candidateSegmentVisited &&
- candidateSegment != null &&
- candidateSegment.getSecondNode() == node
- ) {
- coor = ProjectionRegistry.getProjection().eastNorth2latlon(newPointEN);
- point = newPoint;
- candidateSegmentVisited = true;
- i--;
- } else if (!helpersUseOriginal && newPointEN != null && !alt && !ctrl && node == candidateNode) {
- coor = ProjectionRegistry.getProjection().eastNorth2latlon(newPointEN);
- point = newPoint;
- } else if (!helpersUseOriginal && alt && !ctrl && node == candidateNode) {
- continue;
- } else {
- coor = node.getCoor();
- point = mv.getPoint(coor);
- }
- if (nodeCounter >= 1) {
- heading = fixHeading(-90+lastcoor.bearing(coor)*180/Math.PI);
- distance = lastcoor.greatCircleDistance(coor);
- if (nodeCounter >= 2) {
- turn = Math.abs(fixHeading(heading-lastheading));
- double fixedHeading = fixHeading(heading - lastheading);
- g.setColor(turnColor);
- drawDisplacedlabel(
- lastpoint.x,
- lastpoint.y,
- turnTextDistance,
- (lastheading + fixedHeading/2 + (fixedHeading >= 0 ? 90 : -90))*Math.PI/180,
- String.format("%1.0f °", turn),
- g
- );
- arcRadius = arcRadiusPixels;
- arc = new Arc2D.Double(
- lastpoint.x-arcRadius,
- lastpoint.y-arcRadius,
- arcRadius*2,
- arcRadius*2,
- -heading + (fixedHeading >= 0 ? 90 : -90),
- fixedHeading,
- Arc2D.PIE
- );
- g.setStroke(arcStroke);
- g.setColor(arcFillColor);
- g.fill(arc);
- g.setColor(arcStrokeColor);
- g.draw(arc);
- }
-
- // Display segment length
- // avoid doubling first segment on closed ways
- if (i != nodesCount) {
- g.setColor(distanceColor);
- drawDisplacedlabel(
- (lastpoint.x+point.x)/2,
- (lastpoint.y+point.y)/2,
- distanceTextDistance,
- (heading + 90)*Math.PI/180,
- String.format("%1.0f m", distance),
- g
- );
- }
-
- lastheading = heading;
- }
- lastcoor = coor;
- lastpoint = point;
- nodeCounter++;
- }
-
- // Find and display point where turn angle will be same with two neighbours
- EastNorth equalAngleEN = findEqualAngleEN();
- if (equalAngleEN != null) {
- Point equalAnglePoint = mv.getPoint(equalAngleEN);
- Ellipse2D.Double equalAngleCircle = new Ellipse2D.Double(
- equalAnglePoint.x-equalAngleCircleRadius/2,
- equalAnglePoint.y-equalAngleCircleRadius/2,
- equalAngleCircleRadius,
- equalAngleCircleRadius);
- g.setStroke(equalAngleCircleStroke);
- g.setColor(equalAngleCircleColor);
- g.draw(equalAngleCircle);
- }
- }
- }
-
- // returns node index for closed ways using possibly under/overflowed index
- // returns -1 if not closed and out of range
- private int fixIndex(int count, boolean closed, int index) {
- if (index >= 0 && index < count) return index;
- if (!closed) return -1;
- while (index < 0) index += count;
- while (index >= count) index -= count;
- return index;
- }
-
- private double fixHeading(double heading) {
- while (heading < -180) heading += 360;
- while (heading > 180) heading -= 360;
- return heading;
- }
-
- public static void drawDisplacedlabel(
- int x,
- int y,
- int distance,
- double heading,
- String labelText,
- Graphics2D g
- ) {
- int labelWidth, labelHeight;
- FontMetrics fontMetrics = g.getFontMetrics();
- labelWidth = fontMetrics.stringWidth(labelText);
- labelHeight = fontMetrics.getHeight();
- g.drawString(
- labelText,
- (int) (x+(distance+(labelWidth-labelHeight)/2)*Math.cos(heading)-labelWidth/2),
- (int) (y+distance*Math.sin(heading)+labelHeight/2)
- );
- }
-
- public EastNorth getNewPointEN() {
- if (mod4) {
- return findEqualAngleEN();
- } else if (mousePos != null) {
- return mv.getEastNorth(mousePos.x, mousePos.y);
- } else {
- return null;
- }
- }
-
- public EastNorth findEqualAngleEN() {
- int index1 = -1;
- int index2 = -1;
- int realNodesCount = targetWay.getRealNodesCount();
-
- for (int i = 0; i < realNodesCount; i++) {
- Node node = targetWay.getNode(i);
- if (node == candidateNode) {
- index1 = i-1;
- index2 = i+1;
- }
- if (candidateSegment != null) {
- if (node == candidateSegment.getFirstNode()) index1 = i;
- if (node == candidateSegment.getSecondNode()) index2 = i;
- }
- }
-
- int i11 = fixIndex(realNodesCount, targetWay.isClosed(), index1-1);
- int i12 = fixIndex(realNodesCount, targetWay.isClosed(), index1);
- int i21 = fixIndex(realNodesCount, targetWay.isClosed(), index2);
- int i22 = fixIndex(realNodesCount, targetWay.isClosed(), index2+1);
- if (i11 < 0 || i12 < 0 || i21 < 0 || i22 < 0) return null;
-
- EastNorth p11 = targetWay.getNode(i11).getEastNorth();
- EastNorth p12 = targetWay.getNode(i12).getEastNorth();
- EastNorth p21 = targetWay.getNode(i21).getEastNorth();
- EastNorth p22 = targetWay.getNode(i22).getEastNorth();
-
- double a1 = Geometry.getSegmentAngle(p11, p12);
- double a2 = Geometry.getSegmentAngle(p21, p22);
- double a = fixHeading((a2-a1)*180/Math.PI)*Math.PI/180/3;
-
- EastNorth p1r = p11.rotate(p12, -a);
- EastNorth p2r = p22.rotate(p21, a);
-
- return Geometry.getLineLineIntersection(p1r, p12, p21, p2r);
- }
-
- protected void drawIntersectingWayHelperLines(MapView mv, GeneralPath b, Point newPoint) {
- for (final OsmPrimitive referrer : candidateNode.getReferrers()) {
- if (!(referrer instanceof Way) || targetWay.equals(referrer)) {
- continue;
- }
- final List nodes = ((Way) referrer).getNodes();
- for (int i = 0; i < nodes.size(); i++) {
- if (!candidateNode.equals(nodes.get(i))) {
- continue;
- }
- if (i > 0) {
- final Point p = mv.getPoint(nodes.get(i - 1));
- b.moveTo(newPoint.x, newPoint.y);
- b.lineTo(p.x, p.y);
- }
- if (i < nodes.size() - 1) {
- final Point p = mv.getPoint(nodes.get(i + 1));
- b.moveTo(newPoint.x, newPoint.y);
- b.lineTo(p.x, p.y);
- }
- }
- }
- }
-
- // -------------------------------------------------------------------------
- // Event handlers
- // -------------------------------------------------------------------------
- @Override
- public void modifiersExChanged(int modifiers) {
- if (!MainApplication.isDisplayingMapView() || !MainApplication.getMap().mapView.isActiveLayerDrawable()) {
- return;
- }
- updateKeyModifiersEx(modifiers);
- updateCursorDependentObjectsIfNeeded();
- updateCursor();
- updateStatusLine();
- MainApplication.getLayerManager().invalidateEditLayer();
- }
-
- @Override
- public void selectionChanged(SelectionChangeEvent event) {
- if (selectionChangedBlocked) {
- return;
- }
- updateStateByCurrentSelection();
- }
-
- @Override
- public void mouseDragged(MouseEvent e) {
- dragging = true;
- mouseMoved(e);
- }
-
- @Override
- public void mouseMoved(MouseEvent e) {
- if (!isEnabled()) {
- return;
- }
-
- mousePos = e.getPoint();
-
- updateKeyModifiers(e);
- updateCursorDependentObjectsIfNeeded();
- updateCursor();
- updateStatusLine();
- MainApplication.getLayerManager().invalidateEditLayer();
- }
-
- @Override
- public void mouseReleased(MouseEvent e) {
- dragging = false;
- if (!isEnabled() || e.getButton() != MouseEvent.BUTTON1) {
- return;
- }
-
- updateKeyModifiers(e);
- mousePos = e.getPoint();
- EastNorth newPointEN = getNewPointEN();
-
- if (state == State.selecting) {
- if (targetWay != null) {
- getLayerManager().getEditDataSet().setSelected(targetWay.getPrimitiveId());
- updateStateByCurrentSelection();
- }
- } else if (state == State.improving && newPointEN != null) {
- // Checking if the new coordinate is outside of the world
- if (new Node(newPointEN).isOutSideWorld()) {
- JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
- tr("Cannot add a node outside of the world."),
- tr("Warning"), JOptionPane.WARNING_MESSAGE);
- return;
- }
-
- if (ctrl && !alt && candidateSegment != null) {
- // Adding a new node to the highlighted segment
- // Important: If there are other ways containing the same
- // segment, a node must added to all of that ways.
- Collection virtualCmds = new LinkedList<>();
-
- // Creating a new node
- Node virtualNode = new Node(
- ProjectionRegistry.getProjection().eastNorth2latlon(newPointEN)
- );
- virtualCmds.add(new AddCommand(getLayerManager().getEditDataSet(), virtualNode));
-
- // Looking for candidateSegment copies in ways that are
- // referenced
- // by candidateSegment nodes
- List firstNodeWays = new ArrayList<>(Utils.filteredCollection(
- candidateSegment.getFirstNode().getReferrers(),
- Way.class));
- List secondNodeWays = new ArrayList<>(Utils.filteredCollection(
- candidateSegment.getFirstNode().getReferrers(),
- Way.class));
-
- Collection> virtualSegments = new LinkedList<>();
- for (Way w : firstNodeWays) {
- List> wpps = w.getNodePairs(true);
- for (Way w2 : secondNodeWays) {
- if (!w.equals(w2)) {
- continue;
- }
- // A way is referenced in both nodes.
- // Checking if there is such segment
- int i = -1;
- for (Pair wpp : wpps) {
- ++i;
- boolean ab = wpp.a.equals(candidateSegment.getFirstNode())
- && wpp.b.equals(candidateSegment.getSecondNode());
- boolean ba = wpp.b.equals(candidateSegment.getFirstNode())
- && wpp.a.equals(candidateSegment.getSecondNode());
- if (ab || ba) {
- virtualSegments.add(new IWaySegment<>(w, i));
- }
- }
- }
- }
-
- // Adding the node to all segments found
- for (IWaySegment, Way> virtualSegment : virtualSegments) {
- Way w = virtualSegment.getWay();
- Way wnew = new Way(w);
- wnew.addNode(virtualSegment.getUpperIndex(), virtualNode);
- virtualCmds.add(new ChangeCommand(w, wnew));
- }
-
- // Finishing the sequence command
- String text = trn("Add a new node to way",
- "Add a new node to {0} ways",
- virtualSegments.size(), virtualSegments.size());
-
- UndoRedoHandler.getInstance().add(new SequenceCommand(text, virtualCmds));
-
- } else if (alt && !ctrl && candidateNode != null) {
- // Deleting the highlighted node
-
- //check to see if node is in use by more than one object
- List referrers = candidateNode.getReferrers();
- Collection ways = Utils.filteredCollection(referrers, Way.class);
- if (referrers.size() != 1 || ways.size() != 1) {
- // detach node from way
- final Way newWay = new Way(targetWay);
- final List nodes = newWay.getNodes();
- nodes.remove(candidateNode);
- newWay.setNodes(nodes);
- UndoRedoHandler.getInstance().add(new ChangeCommand(targetWay, newWay));
- } else if (candidateNode.isTagged()) {
- JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
- tr("Cannot delete node that has tags"),
- tr("Error"), JOptionPane.ERROR_MESSAGE);
- } else {
- List nodeList = new ArrayList<>();
- nodeList.add(candidateNode);
- Command deleteCmd = DeleteCommand.delete(nodeList, true);
- if (deleteCmd != null) {
- UndoRedoHandler.getInstance().add(deleteCmd);
- }
- }
-
-
- } else if (candidateNode != null) {
- // Moving the highlighted node
- EastNorth nodeEN = candidateNode.getEastNorth();
-
- Node saveCandidateNode = candidateNode;
- UndoRedoHandler.getInstance().add(new MoveCommand(candidateNode, newPointEN.east() - nodeEN.east(), newPointEN.north()
- - nodeEN.north()));
- candidateNode = saveCandidateNode;
-
- }
- }
-
- updateCursor();
- updateStatusLine();
- MainApplication.getLayerManager().invalidateEditLayer();
- }
-
- @Override
- public void mouseExited(MouseEvent e) {
- if (!isEnabled()) {
- return;
- }
-
- if (!dragging) {
- mousePos = null;
- }
- MainApplication.getLayerManager().invalidateEditLayer();
- }
-
- // -------------------------------------------------------------------------
- // Custom methods
- // -------------------------------------------------------------------------
- /**
- * Sets new cursor depending on state, mouse position
- */
- private void updateCursor() {
- if (!isEnabled()) {
- mv.setNewCursor(null, this);
- return;
- }
-
- if (state == State.selecting) {
- mv.setNewCursor(targetWay == null ? cursorSelect
- : cursorSelectHover, this);
- } else if (state == State.improving) {
- if (alt && !ctrl) {
- mv.setNewCursor(cursorImproveDelete, this);
- } else if (shift || dragging) {
- if (ctrl) {
- mv.setNewCursor(cursorImproveAddLock, this);
- } else {
- mv.setNewCursor(cursorImproveLock, this);
- }
- } else if (ctrl && !alt) {
- mv.setNewCursor(cursorImproveAdd, this);
- } else {
- mv.setNewCursor(cursorImprove, this);
- }
- }
- }
-
- /**
- * Updates these objects under cursor: targetWay, candidateNode,
- * candidateSegment
- */
- public void updateCursorDependentObjectsIfNeeded() {
- if (state == State.improving && (shift || dragging)
- && !(candidateNode == null && candidateSegment == null)) {
- return;
- }
-
- if (mousePos == null) {
- candidateNode = null;
- candidateSegment = null;
- return;
- }
-
- if (state == State.selecting) {
- targetWay = ImproveWayAccuracyHelper.findWay(mv, mousePos);
- } else if (state == State.improving) {
- if (ctrl && !alt) {
- candidateSegment = ImproveWayAccuracyHelper.findCandidateSegment(mv,
- targetWay, mousePos);
- candidateNode = null;
- } else {
- candidateNode = ImproveWayAccuracyHelper.findCandidateNode(mv,
- targetWay, mousePos);
- candidateSegment = null;
- }
- }
- }
-
- /**
- * Switches to Selecting state
- */
- public void startSelecting() {
- state = State.selecting;
-
- targetWay = null;
-
- MainApplication.getLayerManager().invalidateEditLayer();
- updateStatusLine();
- }
-
- /**
- * Switches to Improving state
- *
- * @param targetWay Way that is going to be improved
- */
- public void startImproving(Way targetWay) {
- state = State.improving;
-
- Collection currentSelection = getLayerManager().getEditDataSet().getSelected();
- if (currentSelection.size() != 1
- || !currentSelection.iterator().next().equals(targetWay)) {
- selectionChangedBlocked = true;
- getLayerManager().getEditDataSet().clearSelection();
- getLayerManager().getEditDataSet().setSelected(targetWay.getPrimitiveId());
- selectionChangedBlocked = false;
- }
-
- this.targetWay = targetWay;
- this.candidateNode = null;
- this.candidateSegment = null;
-
- MainApplication.getLayerManager().invalidateEditLayer();
- updateStatusLine();
- }
-
- /**
- * Updates the state according to the current selection. Goes to Improve
- * state if a single way or node is selected. Extracts a way by a node in
- * the second case.
- *
- */
- private void updateStateByCurrentSelection() {
- final List nodeList = new ArrayList<>();
- final List wayList = new ArrayList<>();
- final DataSet editDataSet = getLayerManager().getEditDataSet();
- if (editDataSet != null) {
- final Collection sel = editDataSet.getSelected();
-
- // Collecting nodes and ways from the selection
- for (OsmPrimitive p : sel) {
- if (p instanceof Way) {
- wayList.add((Way) p);
- }
- if (p instanceof Node) {
- nodeList.add((Node) p);
- }
- }
- }
-
- if (wayList.size() == 1) {
- // Starting improving the single selected way
- startImproving(wayList.get(0));
- return;
- } else if (nodeList.size() == 1) {
- // Starting improving the only way of the single selected node
- List r = nodeList.get(0).getReferrers();
- if (r.size() == 1 && (r.get(0) instanceof Way)) {
- startImproving((Way) r.get(0));
- return;
- }
- }
-
- // Starting selecting by default
- startSelecting();
- }
-
- private void resetTimer() {
- if (longKeypressTimer != null) {
- try {
- longKeypressTimer.cancel();
- longKeypressTimer.purge();
- } catch (IllegalStateException exception) {
- Logging.debug(exception);
- }
- }
- longKeypressTimer = new Timer();
- }
-
- @Override
- public void doKeyPressed(KeyEvent e) {
- if (e.getKeyCode() == KeyEvent.VK_WINDOWS) {
- mod4 = true;
- MainApplication.getLayerManager().invalidateEditLayer();
- return;
- }
- if (!helpersShortcut.isEvent(e) && !getShortcut().isEvent(e)) return;
- if (!isExpert) return;
- keypressTime = System.currentTimeMillis();
- helpersEnabledBeforeKeypressed = helpersEnabled;
- if (!helpersEnabled) helpersEnabled = true;
- helpersUseOriginal = true;
- MainApplication.getLayerManager().invalidateEditLayer();
- }
-
- @Override
- public void doKeyReleased(KeyEvent e) {
- if (e.getKeyCode() == KeyEvent.VK_WINDOWS) {
- mod4 = false;
- MainApplication.getLayerManager().invalidateEditLayer();
- return;
- }
- if (!helpersShortcut.isEvent(e) && !getShortcut().isEvent(e)) return;
- if (!isExpert) return;
- resetTimer();
- long keyupTime = System.currentTimeMillis();
- if (keypressTime == 0) { // comes from enterMode
- helpersEnabled = false;
- } else if (keyupTime-keypressTime > longKeypressTime) {
- helpersEnabled = helpersEnabledBeforeKeypressed;
- } else {
- helpersEnabled = !helpersEnabledBeforeKeypressed;
- }
- helpersUseOriginal = false;
- MainApplication.getLayerManager().invalidateEditLayer();
- }
-
- @Override
- public void expertChanged(boolean isExpert) {
- this.isExpert = isExpert;
- if (!isExpert && helpersEnabled) {
- helpersEnabled = false;
- MainApplication.getLayerManager().invalidateEditLayer();
- }
- }
-
- @Override
- public void preferenceChanged(PreferenceChangeEvent e) {
- super.preferenceChanged(e);
- if (isEnabled() && (e.getKey().startsWith("improvewayaccuracy") || e.getKey().startsWith("color.improve.way.accuracy"))) {
- MainApplication.getLayerManager().invalidateEditLayer();
- }
- }
-}
diff --git a/src/org/openstreetmap/josm/plugins/improveway/ImproveWayAction.java b/src/org/openstreetmap/josm/plugins/improveway/ImproveWayAction.java
new file mode 100644
index 0000000..b60e791
--- /dev/null
+++ b/src/org/openstreetmap/josm/plugins/improveway/ImproveWayAction.java
@@ -0,0 +1,347 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.improveway;
+
+import org.openstreetmap.josm.core.patch.ImproveWayAccuracyAction;
+import org.openstreetmap.josm.data.Bounds;
+import org.openstreetmap.josm.data.coor.EastNorth;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.preferences.NamedColorProperty;
+import org.openstreetmap.josm.data.projection.ProjectionRegistry;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.util.GuiHelper;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
+import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.tools.Geometry;
+import org.openstreetmap.josm.tools.Shortcut;
+
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.RenderingHints;
+import java.awt.Stroke;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+
+import static org.openstreetmap.josm.tools.I18n.marktr;
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+/**
+ * @author András Kolesár kolesar@openstreetmap.hu 2016
+ */
+public class ImproveWayAction extends ImproveWayAccuracyAction implements PreferenceChangedListener {
+
+ protected Color turnColor;
+ protected Color distanceColor;
+ protected Color arcFillColor;
+ protected Color arcStrokeColor;
+ protected Color perpendicularLineColor;
+ protected Color equalAngleCircleColor;
+
+ protected transient Stroke arcStroke;
+ protected transient Stroke perpendicularLineStroke;
+ protected transient Stroke equalAngleCircleStroke;
+
+ protected int arcRadiusPixels;
+ protected int perpendicularLengthPixels;
+ protected int turnTextDistance;
+ protected int distanceTextDistance;
+ protected int equalAngleCircleRadius;
+
+ protected ImproveWaySettings settings = new ImproveWaySettings(this, getShortcut());
+
+ public ImproveWayAction() {
+ super(
+ tr("Improve Way"),
+ "improveway",
+ tr("Improve Way mode"),
+ Shortcut.registerShortcut(
+ "mapmode:ImproveWay",
+ tr("Mode: {0}", tr("Improve Way")),
+ KeyEvent.VK_W,
+ Shortcut.DIRECT
+ ),
+ Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)
+ );
+ }
+
+ // -------------------------------------------------------------------------
+ // Mode methods
+ // -------------------------------------------------------------------------
+ @Override
+ public void enterMode() {
+ super.enterMode();
+ settings.onEnterMode();
+ }
+
+ @Override
+ public void exitMode() {
+ super.exitMode();
+ settings.onExitMode();
+ }
+
+ @Override
+ protected void readPreferences() {
+ super.readPreferences();
+ turnColor = new NamedColorProperty(marktr("improve way accuracy helper turn angle text"), new Color(240, 240, 240, 200)).get();
+ distanceColor = new NamedColorProperty(marktr("improve way accuracy helper distance text"), new Color(240, 240, 240, 120)).get();
+ arcFillColor = new NamedColorProperty(marktr("improve way accuracy helper arc fill"), new Color(200, 200, 200, 50)).get();
+ arcStrokeColor = new NamedColorProperty(marktr("improve way accuracy helper arc stroke"), new Color(240, 240, 240, 150)).get();
+ perpendicularLineColor = new NamedColorProperty(marktr("improve way accuracy helper perpendicular line"),
+ new Color(240, 240, 240, 150)).get();
+ equalAngleCircleColor = new NamedColorProperty(marktr("improve way accuracy helper equal angle circle"),
+ new Color(240, 240, 240, 150)).get();
+
+ arcStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.helper-arc", "1"));
+ perpendicularLineStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.helper-perpendicular-line", "1 6"));
+ equalAngleCircleStroke = GuiHelper.getCustomizedStroke(Config.getPref().get("improvewayaccuracy.stroke.helper-eual-angle-circle", "1"));
+
+ arcRadiusPixels = Config.getPref().getInt("improvewayaccuracy.helper-arc-radius", 200);
+ perpendicularLengthPixels = Config.getPref().getInt("improvewayaccuracy.helper-perpendicular-line-length", 100);
+ turnTextDistance = Config.getPref().getInt("improvewayaccuracy.helper-turn-text-distance", 15);
+ distanceTextDistance = Config.getPref().getInt("improvewayaccuracy.helper-distance-text-distance", 15);
+ equalAngleCircleRadius = Config.getPref().getInt("improvewayaccuracy.helper-equal-angle-circle-radius", 15);
+ settings.longKeypressTime = Config.getPref().getInt("improvewayaccuracy.long-keypress-time", 250);
+ }
+
+ @Override
+ public void paint(Graphics2D g, MapView mv, Bounds bbox) {
+ super.paint(g, mv, bbox);
+
+ if (state == State.IMPROVING) {
+ // Painting helpers visualizing turn angles and more
+ if (!settings.helpersEnabled) return;
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ drawHalfDistanceLine(g, mv);
+ drawTurnAnglePie(g, mv);
+ drawEqualAnglePoint(g, mv);
+ }
+ }
+
+ /**
+ * Draw a perpendicular line at half distance between two endpoints
+ */
+ protected void drawHalfDistanceLine(Graphics2D g, MapView mv) {
+ if (!(alt && !ctrl) && endpoint1 != null && endpoint2 != null) {
+ Point p1 = mv.getPoint(endpoint1);
+ Point p2 = mv.getPoint(endpoint2);
+ Point half = new Point(
+ (p1.x + p2.x)/2,
+ (p1.y + p2.y)/2
+ );
+ double heading = Math.atan2(
+ p2.y-p1.y,
+ p2.x-p1.x
+ ) + Math.PI/2;
+ g.setStroke(perpendicularLineStroke);
+ g.setColor(perpendicularLineColor);
+ g.draw(new Line2D.Double(
+ half.x + perpendicularLengthPixels * Math.cos(heading),
+ half.y + perpendicularLengthPixels * Math.sin(heading),
+ half.x - perpendicularLengthPixels * Math.cos(heading),
+ half.y - perpendicularLengthPixels * Math.sin(heading)
+ ));
+ }
+ }
+
+ /**
+ * Draw a pie (part of a circle) representing turn angle at each node
+ */
+ protected void drawTurnAnglePie(Graphics2D g, MapView mv) {
+ LatLon lastcoor = null;
+ Point lastpoint = null;
+ double lastheading = 0d;
+ boolean candidateSegmentVisited = false;
+ int nodeCounter = 0;
+
+ if (targetWay == null) return;
+ int nodesCount = targetWay.getNodesCount();
+ int endLoop = nodesCount;
+ if (targetWay.isClosed()) endLoop++;
+
+ LatLon newLatLon = getNewLatLon();
+ Point newPoint = mv.getPoint(newLatLon);
+
+ for (int i = 0; i < endLoop; i++) {
+ // when way is closed we visit second node again
+ // to get turn for start/end node
+ Node node = targetWay.getNode(i == nodesCount ? 1 : i);
+ LatLon coor;
+ Point point;
+ if (!settings.helpersUseOriginal && newLatLon != null &&
+ ctrl &&
+ !candidateSegmentVisited &&
+ candidateSegment != null &&
+ candidateSegment.getSecondNode() == node
+ ) {
+ coor = newLatLon;
+ point = newPoint;
+ candidateSegmentVisited = true;
+ i--;
+ } else if (!settings.helpersUseOriginal && newLatLon != null && !alt && !ctrl && node == candidateNode) {
+ coor = newLatLon;
+ point = newPoint;
+ } else if (!settings.helpersUseOriginal && alt && !ctrl && node == candidateNode) {
+ continue;
+ } else {
+ coor = node.getCoor();
+ point = mv.getPoint(coor);
+ }
+ if (nodeCounter >= 1 && lastcoor != null && lastpoint != null) {
+ double heading = ImproveWayHelper.fixHeading(-90+lastcoor.bearing(coor)*180/Math.PI);
+ double distance = lastcoor.greatCircleDistance(coor);
+ if (nodeCounter >= 2) {
+ double turn = Math.abs(ImproveWayHelper.fixHeading(heading-lastheading));
+ double fixedHeading = ImproveWayHelper.fixHeading(heading - lastheading);
+ g.setColor(turnColor);
+ ImproveWayHelper.drawDisplacedlabel(
+ lastpoint.x,
+ lastpoint.y,
+ turnTextDistance,
+ (lastheading + fixedHeading/2 + (fixedHeading >= 0 ? 90 : -90))*Math.PI/180,
+ String.format("%1.0f °", turn),
+ g
+ );
+ double arcRadius = arcRadiusPixels;
+ Arc2D arc = new Arc2D.Double(
+ lastpoint.x-arcRadius,
+ lastpoint.y-arcRadius,
+ arcRadius*2,
+ arcRadius*2,
+ -heading + (fixedHeading >= 0 ? 90 : -90),
+ fixedHeading,
+ Arc2D.PIE
+ );
+ g.setStroke(arcStroke);
+ g.setColor(arcFillColor);
+ g.fill(arc);
+ g.setColor(arcStrokeColor);
+ g.draw(arc);
+ }
+
+ // Display segment length
+ // avoid doubling first segment on closed ways
+ if (i != nodesCount) {
+ g.setColor(distanceColor);
+ ImproveWayHelper.drawDisplacedlabel(
+ (lastpoint.x+point.x)/2,
+ (lastpoint.y+point.y)/2,
+ distanceTextDistance,
+ (heading + 90)*Math.PI/180,
+ String.format("%1.0f m", distance),
+ g
+ );
+ }
+
+ lastheading = heading;
+ }
+ lastcoor = coor;
+ lastpoint = point;
+ nodeCounter++;
+ }
+ }
+
+ /**
+ * Draw a point where turn angle will be same with two neighbours
+ */
+ protected void drawEqualAnglePoint(Graphics2D g, MapView mv) {
+ LatLon equalAngleLatLon = findEqualAngleLatLon();
+ if (equalAngleLatLon != null) {
+ Point equalAnglePoint = mv.getPoint(equalAngleLatLon);
+ Ellipse2D.Double equalAngleCircle = new Ellipse2D.Double(
+ equalAnglePoint.x-equalAngleCircleRadius/2d,
+ equalAnglePoint.y-equalAngleCircleRadius/2d,
+ equalAngleCircleRadius,
+ equalAngleCircleRadius);
+ g.setStroke(equalAngleCircleStroke);
+ g.setColor(equalAngleCircleColor);
+ g.draw(equalAngleCircle);
+ }
+ }
+
+ protected LatLon getNewLatLon() {
+ if (settings.meta) {
+ return findEqualAngleLatLon();
+ } else if (mousePos != null) {
+ return mv.getLatLon(mousePos.x, mousePos.y);
+ } else {
+ return null;
+ }
+ }
+
+ protected void setMouseToEqualAnglePoint() {
+ mousePos = mv.getPoint(findEqualAngleLatLon());
+ }
+
+ protected LatLon findEqualAngleLatLon() {
+ int index1 = -1;
+ int index2 = -1;
+ if (targetWay == null) return null;
+ int realNodesCount = targetWay.getRealNodesCount();
+
+ for (int i = 0; i < realNodesCount; i++) {
+ Node node = targetWay.getNode(i);
+ if (node == candidateNode) {
+ index1 = i-1;
+ index2 = i+1;
+ }
+ if (candidateSegment != null) {
+ if (node == candidateSegment.getFirstNode()) index1 = i;
+ if (node == candidateSegment.getSecondNode()) index2 = i;
+ }
+ }
+
+ int i11 = ImproveWayHelper.fixIndex(realNodesCount, targetWay.isClosed(), index1-1);
+ int i12 = ImproveWayHelper.fixIndex(realNodesCount, targetWay.isClosed(), index1);
+ int i21 = ImproveWayHelper.fixIndex(realNodesCount, targetWay.isClosed(), index2);
+ int i22 = ImproveWayHelper.fixIndex(realNodesCount, targetWay.isClosed(), index2+1);
+ if (i11 < 0 || i12 < 0 || i21 < 0 || i22 < 0) return null;
+
+ EastNorth p11 = targetWay.getNode(i11).getEastNorth();
+ EastNorth p12 = targetWay.getNode(i12).getEastNorth();
+ EastNorth p21 = targetWay.getNode(i21).getEastNorth();
+ EastNorth p22 = targetWay.getNode(i22).getEastNorth();
+
+ double a1 = Geometry.getSegmentAngle(p11, p12);
+ double a2 = Geometry.getSegmentAngle(p21, p22);
+ double a = ImproveWayHelper.fixHeading((a2-a1)*180/Math.PI)*Math.PI/180/3;
+
+ EastNorth p1r = p11.rotate(p12, -a);
+ EastNorth p2r = p22.rotate(p21, a);
+
+ EastNorth intersection = Geometry.getLineLineIntersection(p1r, p12, p21, p2r);
+ return ProjectionRegistry.getProjection().eastNorth2latlon(intersection);
+ }
+
+ @Override
+ protected void updateMousePosition(MouseEvent e) {
+ if (!settings.meta) {
+ super.updateMousePosition(e);
+ } else {
+ setMouseToEqualAnglePoint();
+ }
+ }
+
+ @Override
+ public void updateCursorDependentObjectsIfNeeded() {
+ if (!settings.meta) {
+ super.updateCursorDependentObjectsIfNeeded();
+ }
+ }
+
+ @Override
+ public void preferenceChanged(PreferenceChangeEvent e) {
+ super.preferenceChanged(e);
+ if (isEnabled() && (e.getKey().startsWith("improvewayaccuracy") || e.getKey().startsWith("color.improve.way.accuracy"))) {
+ MainApplication.getLayerManager().invalidateEditLayer();
+ }
+ }
+
+}
diff --git a/src/org/openstreetmap/josm/plugins/improveway/ImproveWayHelper.java b/src/org/openstreetmap/josm/plugins/improveway/ImproveWayHelper.java
new file mode 100644
index 0000000..91e4a76
--- /dev/null
+++ b/src/org/openstreetmap/josm/plugins/improveway/ImproveWayHelper.java
@@ -0,0 +1,41 @@
+package org.openstreetmap.josm.plugins.improveway;
+
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+
+public class ImproveWayHelper {
+ public static void drawDisplacedlabel(
+ int x,
+ int y,
+ int distance,
+ double heading,
+ String labelText,
+ Graphics2D g
+ ) {
+ int labelWidth, labelHeight;
+ FontMetrics fontMetrics = g.getFontMetrics();
+ labelWidth = fontMetrics.stringWidth(labelText);
+ labelHeight = fontMetrics.getHeight();
+ g.drawString(
+ labelText,
+ (int) (x + (distance + (labelWidth - labelHeight) / 2) * Math.cos(heading) - labelWidth / 2),
+ (int) (y + distance * Math.sin(heading) + labelHeight / 2)
+ );
+ }
+
+ // returns node index for closed ways using possibly under/overflowed index
+ // returns -1 if not closed and out of range
+ protected static int fixIndex(int count, boolean closed, int index) {
+ if (index >= 0 && index < count) return index;
+ if (!closed) return -1;
+ while (index < 0) index += count;
+ while (index >= count) index -= count;
+ return index;
+ }
+
+ protected static double fixHeading(double heading) {
+ while (heading < -180) heading += 360;
+ while (heading > 180) heading -= 360;
+ return heading;
+ }
+}
diff --git a/src/org/openstreetmap/josm/plugins/improveway/ImproveWayPlugin.java b/src/org/openstreetmap/josm/plugins/improveway/ImproveWayPlugin.java
index f644544..b33f2bf 100644
--- a/src/org/openstreetmap/josm/plugins/improveway/ImproveWayPlugin.java
+++ b/src/org/openstreetmap/josm/plugins/improveway/ImproveWayPlugin.java
@@ -1,22 +1,22 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.improveway;
-
-import org.openstreetmap.josm.gui.IconToggleButton;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.MapFrame;
-import org.openstreetmap.josm.plugins.Plugin;
-import org.openstreetmap.josm.plugins.PluginInformation;
-
-public class ImproveWayPlugin extends Plugin {
-
- public ImproveWayPlugin(final PluginInformation info) {
- super(info);
- }
-
- @Override
- public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
- if (oldFrame == null && newFrame != null) {
- MainApplication.getMap().addMapMode(new IconToggleButton(new ImproveWayAccuracyAction(), false));
- }
- }
-}
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.improveway;
+
+import org.openstreetmap.josm.gui.IconToggleButton;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+
+public class ImproveWayPlugin extends Plugin {
+
+ public ImproveWayPlugin(final PluginInformation info) {
+ super(info);
+ }
+
+ @Override
+ public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+ if (oldFrame == null && newFrame != null) {
+ MainApplication.getMap().addMapMode(new IconToggleButton(new ImproveWayAction(), false));
+ }
+ }
+}
diff --git a/src/org/openstreetmap/josm/plugins/improveway/ImproveWaySettings.java b/src/org/openstreetmap/josm/plugins/improveway/ImproveWaySettings.java
new file mode 100644
index 0000000..4fed2f4
--- /dev/null
+++ b/src/org/openstreetmap/josm/plugins/improveway/ImproveWaySettings.java
@@ -0,0 +1,115 @@
+package org.openstreetmap.josm.plugins.improveway;
+
+import org.openstreetmap.josm.actions.ExpertToggleAction;
+import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.util.KeyPressReleaseListener;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.Shortcut;
+
+import java.awt.event.KeyEvent;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class ImproveWaySettings implements KeyPressReleaseListener, ExpertModeChangeListener {
+
+ protected boolean helpersEnabled;
+ protected boolean helpersUseOriginal;
+ protected boolean helpersEnabledBeforeKeypressed;
+ protected long keypressTime;
+ protected long longKeypressTime;
+ protected Timer longKeypressTimer;
+ protected boolean isExpert;
+ protected boolean meta; // Windows/Super/Meta key
+
+ protected final transient ImproveWayAction improveWayAction;
+ protected final transient Shortcut helpersShortcut;
+
+ public ImproveWaySettings(ImproveWayAction improveWayAction, Shortcut helpersShortcut) {
+ this.improveWayAction = improveWayAction;
+ this.helpersShortcut = helpersShortcut;
+ ExpertToggleAction.addExpertModeChangeListener(this, true);
+ }
+
+ void onEnterMode() {
+ MainApplication.getMap().keyDetector.addKeyListener(this);
+ if (!isExpert) return;
+ meta = false;
+ helpersEnabled = false;
+ keypressTime = 0;
+ resetTimer();
+ longKeypressTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ helpersEnabled = true;
+ helpersUseOriginal = true;
+ MainApplication.getLayerManager().invalidateEditLayer();
+ }
+ }, longKeypressTime);
+ }
+
+ void onExitMode() {
+ MainApplication.getMap().keyDetector.removeKeyListener(this);
+ }
+
+ @Override
+ public void doKeyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_WINDOWS) {
+ meta = true;
+ improveWayAction.setMouseToEqualAnglePoint();
+ MainApplication.getLayerManager().invalidateEditLayer();
+ return;
+ }
+ if (!helpersShortcut.isEvent(e)) return;
+ if (!isExpert) return;
+ keypressTime = System.currentTimeMillis();
+ helpersEnabledBeforeKeypressed = helpersEnabled;
+ if (!helpersEnabled) helpersEnabled = true;
+ helpersUseOriginal = true;
+ MainApplication.getLayerManager().invalidateEditLayer();
+ }
+
+ @Override
+ public void doKeyReleased(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_WINDOWS) {
+ meta = false;
+ MainApplication.getLayerManager().invalidateEditLayer();
+ return;
+ }
+ if (!helpersShortcut.isEvent(e)) return;
+ if (!isExpert) return;
+ resetTimer();
+ long keyupTime = System.currentTimeMillis();
+ if (keypressTime == 0) { // comes from enterMode
+ helpersEnabled = false;
+ } else if (keyupTime - keypressTime > longKeypressTime) {
+ helpersEnabled = helpersEnabledBeforeKeypressed;
+ } else {
+ helpersEnabled = !helpersEnabledBeforeKeypressed;
+ }
+ helpersUseOriginal = false;
+ MainApplication.getLayerManager().invalidateEditLayer();
+ }
+
+ protected void resetTimer() {
+ if (longKeypressTimer != null) {
+ try {
+ longKeypressTimer.cancel();
+ longKeypressTimer.purge();
+ } catch (IllegalStateException exception) {
+ Logging.debug(exception);
+ }
+ }
+ longKeypressTimer = new Timer();
+ }
+
+ @Override
+ public void expertChanged(boolean isExpert) {
+ this.isExpert = isExpert;
+ if (!isExpert && helpersEnabled) {
+ helpersEnabled = false;
+ MainApplication.getLayerManager().invalidateEditLayer();
+ }
+ }
+
+}