Ticket #7184: 7184.patch

File 7184.patch, 36.3 KB (added by simon04, 8 years ago)
  • new file src/org/openstreetmap/josm/actions/mapmode/IWAGeometry.java

    diff --git a/images/cursor/hand.png b/images/cursor/hand.png
    new file mode 100644
    index 0000000..ecbdb5a
    Binary files /dev/null and b/images/cursor/hand.png differ
    diff --git a/images/cursor/modifier/add_node.png b/images/cursor/modifier/add_node.png
    new file mode 100644
    index 0000000..2d0dd3f
    Binary files /dev/null and b/images/cursor/modifier/add_node.png differ
    diff --git a/images/cursor/modifier/add_node_lock.png b/images/cursor/modifier/add_node_lock.png
    new file mode 100644
    index 0000000..807f8df
    Binary files /dev/null and b/images/cursor/modifier/add_node_lock.png differ
    diff --git a/images/cursor/modifier/lock.png b/images/cursor/modifier/lock.png
    new file mode 100644
    index 0000000..ab88b88
    Binary files /dev/null and b/images/cursor/modifier/lock.png differ
    diff --git a/images/cursor/modifier/mode.png b/images/cursor/modifier/mode.png
    new file mode 100644
    index 0000000..b018124
    Binary files /dev/null and b/images/cursor/modifier/mode.png differ
    diff --git a/images/mapmode/button.png b/images/mapmode/button.png
    new file mode 100644
    index 0000000..8169968
    Binary files /dev/null and b/images/mapmode/button.png differ
    diff --git a/src/org/openstreetmap/josm/actions/mapmode/IWAGeometry.java b/src/org/openstreetmap/josm/actions/mapmode/IWAGeometry.java
    new file mode 100644
    index 0000000..82fa0d7
    - +  
     1// License: GPL. See LICENSE file for details.
     2package org.openstreetmap.josm.actions.mapmode;
     3
     4import org.openstreetmap.josm.data.coor.EastNorth;
     5import org.openstreetmap.josm.tools.Geometry;
     6
     7/**
     8 * This static class contains geometry functions used by the plugin.
     9 *
     10 * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011
     11 */
     12class IWAGeometry {
     13
     14    /**
     15     * Returns angle of a segment defined with 2 point coordinates.
     16     *
     17     * @param p1
     18     * @param p2
     19     * @return Angle in radians (-pi, pi]
     20     */
     21    public static double getSegmentAngle(EastNorth p1, EastNorth p2) {
     22        return Math.atan2(p2.north() - p1.north(), p2.east() - p1.east());
     23    }
     24
     25    /**
     26     * Returns angle of a corner defined with 3 point coordinates.
     27     *
     28     * @param p1
     29     * @param p2 Common endpoint
     30     * @param p3
     31     * @return Angle in radians (-pi, pi]
     32     */
     33    public static double getCornerAngle(EastNorth p1, EastNorth p2, EastNorth p3) {
     34        Double result = getSegmentAngle(p2, p1) - getSegmentAngle(p2, p3);
     35        if (result <= -Math.PI) {
     36            result += 2 * Math.PI;
     37        }
     38
     39        if (result > Math.PI) {
     40            result -= 2 * Math.PI;
     41        }
     42
     43        return result;
     44    }
     45
     46    /**
     47     * Returns the coordinate of intersection of segment sp1-sp2 and an altitude
     48     * to it starting at point ap. If the line defined with sp1-sp2 intersects
     49     * its altitude out of sp1-sp2, null is returned.
     50     *
     51     * @param sp1
     52     * @param sp2
     53     * @param ap
     54     * @return Intersection coordinate or null
     55     */
     56    public static EastNorth getSegmentAltituteIntersection(EastNorth sp1,
     57            EastNorth sp2, EastNorth ap) {
     58        Double segmentLenght = sp1.distance(sp2);
     59        Double altitudeAngle = getSegmentAngle(sp1, sp2) + Math.PI / 2;
     60
     61        // Taking a random point on the altitude line (angle is known).
     62        EastNorth ap2 = new EastNorth(ap.east() + 1000
     63                * Math.cos(altitudeAngle), ap.north() + 1000
     64                * Math.sin(altitudeAngle));
     65
     66        // Finding the intersection of two lines
     67        EastNorth resultCandidate = Geometry.getLineLineIntersection(sp1, sp2,
     68                ap, ap2);
     69
     70        // Filtering result
     71        if (resultCandidate != null
     72                && resultCandidate.distance(sp1) * .999 < segmentLenght
     73                && resultCandidate.distance(sp2) * .999 < segmentLenght) {
     74            return resultCandidate;
     75        } else {
     76            return null;
     77        }
     78    }
     79}
  • new file src/org/openstreetmap/josm/actions/mapmode/IWATargetWayHelper.java

    diff --git a/src/org/openstreetmap/josm/actions/mapmode/IWATargetWayHelper.java b/src/org/openstreetmap/josm/actions/mapmode/IWATargetWayHelper.java
    new file mode 100644
    index 0000000..981663d
    - +  
     1// License: GPL. See LICENSE file for details.
     2package org.openstreetmap.josm.actions.mapmode;
     3
     4import java.awt.Point;
     5import java.util.Collection;
     6import java.util.List;
     7
     8import org.openstreetmap.josm.Main;
     9import org.openstreetmap.josm.data.coor.EastNorth;
     10import org.openstreetmap.josm.data.osm.Node;
     11import org.openstreetmap.josm.data.osm.OsmPrimitive;
     12import org.openstreetmap.josm.data.osm.Way;
     13import org.openstreetmap.josm.data.osm.WaySegment;
     14import org.openstreetmap.josm.gui.MapView;
     15import org.openstreetmap.josm.tools.Geometry;
     16import org.openstreetmap.josm.tools.Pair;
     17
     18/**
     19 * This static class contains functions used to find target way, node to move or
     20 * segment to divide.
     21 *
     22 * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011
     23 */
     24class IWATargetWayHelper {
     25
     26    /**
     27     * Finds the way to work on. If the mouse is on the node, extracts one of
     28     * the ways containing it. If the mouse is on the way, simply returns it.
     29     *
     30     * @param mv
     31     * @param p
     32     * @return Way or null in case there is nothing under the cursor.
     33     */
     34    public static Way findWay(MapView mv, Point p) {
     35        if (mv == null || p == null) {
     36            return null;
     37        }
     38
     39        Node node = mv.getNearestNode(p, OsmPrimitive.isSelectablePredicate);
     40        Way candidate = null;
     41
     42        if (node != null) {
     43            final Collection<OsmPrimitive> candidates = node.getReferrers();
     44            for (OsmPrimitive refferer : candidates) {
     45                if (refferer instanceof Way) {
     46                    candidate = (Way) refferer;
     47                    break;
     48                }
     49            }
     50            if (candidate != null) {
     51                return candidate;
     52            }
     53        }
     54
     55        candidate = Main.map.mapView.getNearestWay(p,
     56                OsmPrimitive.isSelectablePredicate);
     57
     58        return candidate;
     59    }
     60
     61    /**
     62     * Returns the nearest node to cursor. All nodes that are “behind” segments
     63     * are neglected. This is to avoid way self-intersection after moving the
     64     * candidateNode to a new place.
     65     *
     66     * @param mv
     67     * @param w
     68     * @param p
     69     * @return
     70     */
     71    public static Node findCandidateNode(MapView mv, Way w, Point p) {
     72        if (mv == null || w == null || p == null) {
     73            return null;
     74        }
     75
     76        EastNorth pEN = mv.getEastNorth(p.x, p.y);
     77
     78        Double bestDistance = Double.MAX_VALUE;
     79        Double currentDistance;
     80        List<Pair<Node, Node>> wpps = w.getNodePairs(false);
     81
     82        Node result = null;
     83
     84        mainLoop:
     85        for (Node n : w.getNodes()) {
     86            EastNorth nEN = n.getEastNorth();
     87            currentDistance = pEN.distance(nEN);
     88
     89            if (currentDistance < bestDistance) {
     90                // Making sure this candidate is not behind any segment.
     91                for (Pair<Node, Node> wpp : wpps) {
     92                    if (!wpp.a.equals(n)
     93                            && !wpp.b.equals(n)
     94                            && Geometry.getSegmentSegmentIntersection(
     95                            wpp.a.getEastNorth(), wpp.b.getEastNorth(),
     96                            pEN, nEN) != null) {
     97                        continue mainLoop;
     98                    }
     99                }
     100                result = n;
     101                bestDistance = currentDistance;
     102            }
     103        }
     104
     105        return result;
     106    }
     107
     108    /**
     109     * Returns the nearest way segment to cursor. The distance to segment ab is
     110     * the length of altitude from p to ab (say, c) or the minimum distance from
     111     * p to a or b if c is out of ab.
     112     *
     113     * The priority is given to segments where c is in ab. Otherwise, a segment
     114     * with the largest angle apb is chosen.
     115     *
     116     * @param mv
     117     * @param w
     118     * @param p
     119     * @return
     120     */
     121    public static WaySegment findCandidateSegment(MapView mv, Way w, Point p) {
     122        if (mv == null || w == null || p == null) {
     123            return null;
     124        }
     125
     126        EastNorth pEN = mv.getEastNorth(p.x, p.y);
     127
     128        Double currentDistance;
     129        Double currentAngle;
     130        Double bestDistance = Double.MAX_VALUE;
     131        Double bestAngle = 0.0;
     132
     133        int candidate = -1;
     134
     135        List<Pair<Node, Node>> wpps = w.getNodePairs(true);
     136
     137        int i = -1;
     138        for (Pair<Node, Node> wpp : wpps) {
     139            ++i;
     140
     141            // Finding intersection of the segment with its altitude from p (c)
     142            EastNorth altitudeIntersection = IWAGeometry.getSegmentAltituteIntersection(wpp.a.getEastNorth(),
     143                    wpp.b.getEastNorth(), pEN);
     144
     145            if (altitudeIntersection != null) {
     146                // If the segment intersects with the altitude from p
     147                currentDistance = pEN.distance(altitudeIntersection);
     148
     149                // Making an angle too big to let this candidate win any others
     150                // having the same distance.
     151                currentAngle = Double.MAX_VALUE;
     152
     153            } else {
     154                // Otherwise: Distance is equal to the shortest distance from p
     155                // to a or b
     156                currentDistance = Math.min(pEN.distance(wpp.a.getEastNorth()),
     157                        pEN.distance(wpp.b.getEastNorth()));
     158
     159                // Measuring the angle
     160                currentAngle = Math.abs(IWAGeometry.getCornerAngle(
     161                        wpp.a.getEastNorth(), pEN, wpp.b.getEastNorth()));
     162            }
     163
     164            if (currentDistance < bestDistance
     165                    || (currentAngle > bestAngle && currentDistance < bestDistance * 1.0001 /*
     166                     * equality
     167                     */)) {
     168                candidate = i;
     169                bestAngle = currentAngle;
     170                bestDistance = currentDistance;
     171            }
     172
     173        }
     174        return candidate != -1 ? new WaySegment(w, candidate) : null;
     175    }
     176}
  • new file src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java

    diff --git a/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java b/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java
    new file mode 100644
    index 0000000..0b1b823
    - +  
     1// License: GPL. See LICENSE file for details.
     2package org.openstreetmap.josm.actions.mapmode;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5import static org.openstreetmap.josm.tools.I18n.trn;
     6
     7import java.awt.AWTEvent;
     8import java.awt.BasicStroke;
     9import java.awt.Color;
     10import java.awt.Cursor;
     11import java.awt.Graphics2D;
     12import java.awt.Point;
     13import java.awt.Toolkit;
     14import java.awt.event.AWTEventListener;
     15import java.awt.event.ActionEvent;
     16import java.awt.event.InputEvent;
     17import java.awt.event.KeyEvent;
     18import java.awt.event.MouseEvent;
     19import java.awt.geom.GeneralPath;
     20import java.util.ArrayList;
     21import java.util.Collection;
     22import java.util.Iterator;
     23import java.util.LinkedList;
     24import java.util.List;
     25
     26import javax.swing.JOptionPane;
     27
     28import org.openstreetmap.josm.Main;
     29import org.openstreetmap.josm.command.AddCommand;
     30import org.openstreetmap.josm.command.ChangeCommand;
     31import org.openstreetmap.josm.command.Command;
     32import org.openstreetmap.josm.command.DeleteCommand;
     33import org.openstreetmap.josm.command.MoveCommand;
     34import org.openstreetmap.josm.command.SequenceCommand;
     35import org.openstreetmap.josm.data.Bounds;
     36import org.openstreetmap.josm.data.SelectionChangedListener;
     37import org.openstreetmap.josm.data.coor.EastNorth;
     38import org.openstreetmap.josm.data.osm.DataSet;
     39import org.openstreetmap.josm.data.osm.Node;
     40import org.openstreetmap.josm.data.osm.OsmPrimitive;
     41import org.openstreetmap.josm.data.osm.Way;
     42import org.openstreetmap.josm.data.osm.WaySegment;
     43import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
     44import org.openstreetmap.josm.gui.MapFrame;
     45import org.openstreetmap.josm.gui.MapView;
     46import org.openstreetmap.josm.gui.layer.Layer;
     47import org.openstreetmap.josm.gui.layer.MapViewPaintable;
     48import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     49import org.openstreetmap.josm.tools.ImageProvider;
     50import org.openstreetmap.josm.tools.Pair;
     51import org.openstreetmap.josm.tools.Shortcut;
     52
     53/**
     54 * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011
     55 */
     56public class ImproveWayAccuracyAction extends MapMode implements MapViewPaintable,
     57        SelectionChangedListener, AWTEventListener {
     58
     59    enum State {
     60        selecting, improving
     61    }
     62
     63    private State state;
     64
     65    private MapView mv;
     66
     67    private static final long serialVersionUID = 42L;
     68
     69    private Way targetWay;
     70    private Node candidateNode = null;
     71    private WaySegment candidateSegment = null;
     72
     73    private Point mousePos = null;
     74    private boolean dragging = false;
     75
     76    final private Cursor cursorSelect;
     77    final private Cursor cursorSelectHover;
     78    final private Cursor cursorImprove;
     79    final private Cursor cursorImproveAdd;
     80    final private Cursor cursorImproveDelete;
     81    final private Cursor cursorImproveAddLock;
     82    final private Cursor cursorImproveLock;
     83
     84    private boolean shift = false;
     85    private boolean ctrl = false;
     86    private boolean alt = false;
     87
     88    private final Color guideColor;
     89    private final BasicStroke selectTargetWayStroke;
     90    private final BasicStroke moveNodeStroke;
     91    private final BasicStroke addNodeStroke;
     92    private final BasicStroke deleteNodeStroke;
     93
     94    private boolean selectionChangedBlocked = false;
     95
     96    protected String oldModeHelpText;
     97
     98    public ImproveWayAccuracyAction(MapFrame mapFrame) {
     99        super(tr("Improve Way Accuracy"), "button.png",
     100                tr("Improve Way Accuracy mode"), Shortcut.registerShortcut(
     101                "mapmode:ImproveWayAccuracy",
     102                tr("Mode: {0}", tr("Improve Way Accuracy")),
     103                KeyEvent.VK_K, Shortcut.GROUP_EDIT), mapFrame, Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
     104
     105        cursorSelect = ImageProvider.getCursor("normal", "mode");
     106        cursorSelectHover = ImageProvider.getCursor("hand", "mode");
     107        cursorImprove = ImageProvider.getCursor("crosshair", null);
     108        cursorImproveAdd = ImageProvider.getCursor("crosshair", "addnode");
     109        cursorImproveDelete = ImageProvider.getCursor("crosshair", "delete_node");
     110        cursorImproveAddLock = ImageProvider.getCursor("crosshair",
     111                "add_node_lock");
     112        cursorImproveLock = ImageProvider.getCursor("crosshair", "lock");
     113
     114        guideColor = PaintColors.HIGHLIGHT.get();
     115        selectTargetWayStroke = new BasicStroke(2, BasicStroke.CAP_ROUND,
     116                BasicStroke.JOIN_ROUND);
     117        float dash1[] = {4.0f};
     118        moveNodeStroke = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
     119                BasicStroke.JOIN_MITER, 10.0f, dash1, 0.0f);
     120        addNodeStroke = new BasicStroke(1, BasicStroke.CAP_BUTT,
     121                BasicStroke.JOIN_MITER);
     122        deleteNodeStroke = new BasicStroke(1, BasicStroke.CAP_BUTT,
     123                BasicStroke.JOIN_MITER);
     124    }
     125
     126    // -------------------------------------------------------------------------
     127    // Mode methods
     128    // -------------------------------------------------------------------------
     129    @Override
     130    public void enterMode() {
     131        if (!isEnabled()) {
     132            return;
     133        }
     134        super.enterMode();
     135
     136        mv = Main.map.mapView;
     137        mousePos = null;
     138        oldModeHelpText = "";
     139
     140        if (getCurrentDataSet() == null) {
     141            return;
     142        }
     143
     144        updateStateByCurrentSelection();
     145
     146        Main.map.mapView.addMouseListener(this);
     147        Main.map.mapView.addMouseMotionListener(this);
     148        Main.map.mapView.addTemporaryLayer(this);
     149        DataSet.addSelectionListener(this);
     150
     151        try {
     152            Toolkit.getDefaultToolkit().addAWTEventListener(this,
     153                    AWTEvent.KEY_EVENT_MASK);
     154        } catch (SecurityException ex) {
     155        }
     156    }
     157
     158    @Override
     159    public void exitMode() {
     160        super.exitMode();
     161
     162        Main.map.mapView.removeMouseListener(this);
     163        Main.map.mapView.removeMouseMotionListener(this);
     164        Main.map.mapView.removeTemporaryLayer(this);
     165        DataSet.removeSelectionListener(this);
     166
     167        try {
     168            Toolkit.getDefaultToolkit().removeAWTEventListener(this);
     169        } catch (SecurityException ex) {
     170        }
     171
     172        Main.map.mapView.repaint();
     173    }
     174
     175    @Override
     176    protected void updateStatusLine() {
     177        String newModeHelpText = getModeHelpText();
     178        if (!newModeHelpText.equals(oldModeHelpText)) {
     179            oldModeHelpText = newModeHelpText;
     180            Main.map.statusLine.setHelpText(newModeHelpText);
     181            Main.map.statusLine.repaint();
     182        }
     183    }
     184
     185    @Override
     186    public String getModeHelpText() {
     187        if (state == State.selecting) {
     188            if (targetWay != null) {
     189                return tr("Click on the way to start improving its shape.");
     190            } else {
     191                return tr("Select a way that you want to make more accurate.");
     192            }
     193        } else {
     194            if (ctrl) {
     195                return tr("Click to add a new node. Release Ctrl to move existing nodes or hold Alt to delete.");
     196            } else if (alt) {
     197                return tr("Click to delete the highlighted node. Release Alt to move existing nodes or hold Ctrl to add new nodes.");
     198            } else {
     199                return tr("Click to move the highlighted node. Hold Ctrl to add new nodes, or Alt to delete.");
     200            }
     201        }
     202    }
     203
     204    @Override
     205    public boolean layerIsSupported(Layer l) {
     206        return l instanceof OsmDataLayer;
     207    }
     208
     209    @Override
     210    protected void updateEnabledState() {
     211        setEnabled(getEditLayer() != null);
     212        // setEnabled(Main.main.getActiveLayer() instanceof OsmDataLayer);
     213    }
     214
     215    // -------------------------------------------------------------------------
     216    // MapViewPaintable methods
     217    // -------------------------------------------------------------------------
     218    /**
     219     * Redraws temporary layer. Highlights targetWay in select mode. Draws
     220     * preview lines in improve mode and highlights the candidateNode
     221     */
     222    @Override
     223    public void paint(Graphics2D g, MapView mv, Bounds bbox) {
     224        if (mousePos == null) {
     225            return;
     226        }
     227
     228        g.setColor(guideColor);
     229
     230        if (state == State.selecting && targetWay != null) {
     231            // Highlighting the targetWay in Selecting state
     232            // Non-native highlighting is used, because sometimes highlighted
     233            // segments are covered with others, which is bad.
     234            g.setStroke(selectTargetWayStroke);
     235
     236            List<Node> nodes = targetWay.getNodes();
     237
     238            GeneralPath b = new GeneralPath();
     239            Point p0 = mv.getPoint(nodes.get(0));
     240            Point pn;
     241            b.moveTo(p0.x, p0.y);
     242
     243            for (Node n : nodes) {
     244                pn = mv.getPoint(n);
     245                b.lineTo(pn.x, pn.y);
     246            }
     247            if (targetWay.isClosed()) {
     248                b.lineTo(p0.x, p0.y);
     249            }
     250
     251            g.draw(b);
     252
     253        } else if (state == State.improving) {
     254            // Drawing preview lines and highlighting the node
     255            // that is going to be moved.
     256            // Non-native highlighting is used here as well.
     257
     258            // Finding endpoints
     259            Point p1 = null, p2 = null;
     260            if (ctrl && candidateSegment != null) {
     261                g.setStroke(addNodeStroke);
     262                p1 = mv.getPoint(candidateSegment.getFirstNode());
     263                p2 = mv.getPoint(candidateSegment.getSecondNode());
     264            } else if (!alt && !ctrl && candidateNode != null) {
     265                g.setStroke(moveNodeStroke);
     266                List<Pair<Node, Node>> wpps = targetWay.getNodePairs(false);
     267                for (Pair<Node, Node> wpp : wpps) {
     268                    if (wpp.a == candidateNode) {
     269                        p1 = mv.getPoint(wpp.b);
     270                    }
     271                    if (wpp.b == candidateNode) {
     272                        p2 = mv.getPoint(wpp.a);
     273                    }
     274                    if (p1 != null && p2 != null) {
     275                        break;
     276                    }
     277                }
     278            } else if (alt && !ctrl && candidateNode != null) {
     279                g.setStroke(deleteNodeStroke);
     280                List<Node> nodes = targetWay.getNodes();
     281                int index = nodes.indexOf(candidateNode);
     282
     283                // Only draw line if node is not first and/or last
     284                if (index != 0 && index != (nodes.size() - 1)) {
     285                    p1 = mv.getPoint(nodes.get(index - 1));
     286                    p2 = mv.getPoint(nodes.get(index + 1));
     287                }
     288                // TODO: indicate what part that will be deleted? (for end nodes)
     289            }
     290
     291
     292            // Drawing preview lines
     293            GeneralPath b = new GeneralPath();
     294            if (alt && !ctrl) {
     295                // In delete mode
     296                if (p1 != null && p2 != null) {
     297                    b.moveTo(p1.x, p1.y);
     298                    b.lineTo(p2.x, p2.y);
     299                }
     300            } else {
     301                // In add or move mode
     302                if (p1 != null) {
     303                    b.moveTo(mousePos.x, mousePos.y);
     304                    b.lineTo(p1.x, p1.y);
     305                }
     306                if (p2 != null) {
     307                    b.moveTo(mousePos.x, mousePos.y);
     308                    b.lineTo(p2.x, p2.y);
     309                }
     310            }
     311            g.draw(b);
     312
     313            // Highlighting candidateNode
     314            if (candidateNode != null) {
     315                p1 = mv.getPoint(candidateNode);
     316                g.fillRect(p1.x - 2, p1.y - 2, 6, 6);
     317            }
     318
     319        }
     320    }
     321
     322    // -------------------------------------------------------------------------
     323    // Event handlers
     324    // -------------------------------------------------------------------------
     325    @Override
     326    public void eventDispatched(AWTEvent event) {
     327        if (Main.map == null || Main.map.mapView == null
     328                || !Main.map.mapView.isActiveLayerDrawable()) {
     329            return;
     330        }
     331        updateKeyModifiers((InputEvent) event);
     332        updateCursorDependentObjectsIfNeeded();
     333        updateCursor();
     334        updateStatusLine();
     335        Main.map.mapView.repaint();
     336    }
     337
     338    @Override
     339    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
     340        if (selectionChangedBlocked) {
     341            return;
     342        }
     343        updateStateByCurrentSelection();
     344    }
     345
     346    @Override
     347    public void mouseDragged(MouseEvent e) {
     348        dragging = true;
     349        mouseMoved(e);
     350    }
     351
     352    @Override
     353    public void mouseMoved(MouseEvent e) {
     354        if (!isEnabled()) {
     355            return;
     356        }
     357
     358        mousePos = e.getPoint();
     359
     360        updateKeyModifiers(e);
     361        updateCursorDependentObjectsIfNeeded();
     362        updateCursor();
     363        updateStatusLine();
     364        Main.map.mapView.repaint();
     365    }
     366
     367    @Override
     368    public void mouseReleased(MouseEvent e) {
     369        dragging = false;
     370        if (!isEnabled() || e.getButton() != MouseEvent.BUTTON1) {
     371            return;
     372        }
     373
     374        updateKeyModifiers(e);
     375        mousePos = e.getPoint();
     376
     377        if (state == State.selecting) {
     378            if (targetWay != null) {
     379                getCurrentDataSet().setSelected(targetWay.getPrimitiveId());
     380                updateStateByCurrentSelection();
     381            }
     382        } else if (state == State.improving && mousePos != null) {
     383            // Checking if the new coordinate is outside of the world
     384            if (mv.getLatLon(mousePos.x, mousePos.y).isOutSideWorld()) {
     385                JOptionPane.showMessageDialog(Main.parent,
     386                        tr("Cannot place a node outside of the world."),
     387                        tr("Warning"), JOptionPane.WARNING_MESSAGE);
     388                return;
     389            }
     390
     391            if (ctrl && !alt && candidateSegment != null) {
     392                // Adding a new node to the highlighted segment
     393                // Important: If there are other ways containing the same
     394                // segment, a node must added to all of that ways.
     395                Collection<Command> virtualCmds = new LinkedList<Command>();
     396
     397                // Creating a new node
     398                Node virtualNode = new Node(mv.getEastNorth(mousePos.x,
     399                        mousePos.y));
     400                virtualCmds.add(new AddCommand(virtualNode));
     401
     402                // Looking for candidateSegment copies in ways that are
     403                // referenced
     404                // by candidateSegment nodes
     405                List<Way> firstNodeWays = OsmPrimitive.getFilteredList(
     406                        candidateSegment.getFirstNode().getReferrers(),
     407                        Way.class);
     408                List<Way> secondNodeWays = OsmPrimitive.getFilteredList(
     409                        candidateSegment.getFirstNode().getReferrers(),
     410                        Way.class);
     411
     412                Collection<WaySegment> virtualSegments = new LinkedList<WaySegment>();
     413                for (Way w : firstNodeWays) {
     414                    List<Pair<Node, Node>> wpps = w.getNodePairs(true);
     415                    for (Way w2 : secondNodeWays) {
     416                        if (!w.equals(w2)) {
     417                            continue;
     418                        }
     419                        // A way is referenced in both nodes.
     420                        // Checking if there is such segment
     421                        int i = -1;
     422                        for (Pair<Node, Node> wpp : wpps) {
     423                            ++i;
     424                            if ((wpp.a.equals(candidateSegment.getFirstNode())
     425                                    && wpp.b.equals(candidateSegment.getSecondNode()) || (wpp.b.equals(candidateSegment.getFirstNode()) && wpp.a.equals(candidateSegment.getSecondNode())))) {
     426                                virtualSegments.add(new WaySegment(w, i));
     427                            }
     428                        }
     429                    }
     430                }
     431
     432                // Adding the node to all segments found
     433                for (WaySegment virtualSegment : virtualSegments) {
     434                    Way w = virtualSegment.way;
     435                    Way wnew = new Way(w);
     436                    wnew.addNode(virtualSegment.lowerIndex + 1, virtualNode);
     437                    virtualCmds.add(new ChangeCommand(w, wnew));
     438                }
     439
     440                // Finishing the sequence command
     441                String text = trn("Add and a new node to way",
     442                        "Add and a new node to {0} ways",
     443                        virtualSegments.size(), virtualSegments.size());
     444
     445                Main.main.undoRedo.add(new SequenceCommand(text, virtualCmds));
     446
     447            } else if (alt && !ctrl && candidateNode != null) {
     448                // Deleting the highlighted node
     449
     450                //check to see if node has interesting keys
     451                Iterator<String> keyIterator = candidateNode.getKeys().keySet().iterator();
     452                boolean hasTags = false;
     453                while (keyIterator.hasNext()) {
     454                    String key = keyIterator.next();
     455                    if (!OsmPrimitive.isUninterestingKey(key)) {
     456                        hasTags = true;
     457                        break;
     458                    }
     459                }
     460
     461                //check to see if node is in use by more than one object
     462                List<OsmPrimitive> referrers = candidateNode.getReferrers();
     463                List<Way> ways = OsmPrimitive.getFilteredList(referrers, Way.class);
     464                if (referrers.size() != 1 || ways.size() != 1) {
     465                    JOptionPane.showMessageDialog(Main.parent,
     466                            tr("Cannot delete node that is referenced by multiple objects"),
     467                            tr("Error"), JOptionPane.ERROR_MESSAGE);
     468                } else if (hasTags) {
     469                    JOptionPane.showMessageDialog(Main.parent,
     470                            tr("Cannot delete node that has tags"),
     471                            tr("Error"), JOptionPane.ERROR_MESSAGE);
     472                } else {
     473                    List<Node> nodeList = new ArrayList<Node>();
     474                    nodeList.add(candidateNode);
     475                    Command deleteCmd = DeleteCommand.delete(getEditLayer(), nodeList, true);
     476                    Main.main.undoRedo.add(deleteCmd);
     477                }
     478
     479
     480            } else if (candidateNode != null) {
     481                // Moving the highlighted node
     482                EastNorth nodeEN = candidateNode.getEastNorth();
     483                EastNorth cursorEN = mv.getEastNorth(mousePos.x, mousePos.y);
     484
     485                Main.main.undoRedo.add(new MoveCommand(candidateNode, cursorEN.east() - nodeEN.east(), cursorEN.north()
     486                        - nodeEN.north()));
     487            }
     488        }
     489
     490        mousePos = null;
     491        updateCursor();
     492        updateStatusLine();
     493        Main.map.mapView.repaint();
     494    }
     495
     496    @Override
     497    public void mouseExited(MouseEvent e) {
     498        if (!isEnabled()) {
     499            return;
     500        }
     501
     502        if (!dragging) {
     503            mousePos = null;
     504        }
     505        Main.map.mapView.repaint();
     506    }
     507
     508    // -------------------------------------------------------------------------
     509    // Custom methods
     510    // -------------------------------------------------------------------------
     511    /**
     512     * Updates shift and ctrl key states
     513     */
     514    @Override
     515    protected void updateKeyModifiers(InputEvent e) {
     516        ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
     517        shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     518        // accept either Alt key (including AltGr)
     519        alt = ((e.getModifiers() & (ActionEvent.ALT_MASK | InputEvent.ALT_GRAPH_MASK)) != 0);
     520    }
     521
     522    /**
     523     * Sets new cursor depending on state, mouse position
     524     */
     525    private void updateCursor() {
     526        if (!isEnabled()) {
     527            mv.setNewCursor(null, this);
     528            return;
     529        }
     530
     531        if (state == State.selecting) {
     532            mv.setNewCursor(targetWay == null ? cursorSelect
     533                    : cursorSelectHover, this);
     534        } else if (state == State.improving) {
     535            if (alt && !ctrl) {
     536                mv.setNewCursor(cursorImproveDelete, this);
     537            } else if (shift || dragging) {
     538                if (ctrl) {
     539                    mv.setNewCursor(cursorImproveAddLock, this);
     540                } else {
     541                    mv.setNewCursor(cursorImproveLock, this);
     542                }
     543            } else if (ctrl && !alt) {
     544                mv.setNewCursor(cursorImproveAdd, this);
     545            } else {
     546                mv.setNewCursor(cursorImprove, this);
     547            }
     548        }
     549    }
     550
     551    /**
     552     * Updates these objects under cursor: targetWay, candidateNode,
     553     * candidateSegment
     554     */
     555    public void updateCursorDependentObjectsIfNeeded() {
     556        if (state == State.improving && (shift || dragging)
     557                && !(candidateNode == null && candidateSegment == null)) {
     558            return;
     559        }
     560
     561        if (mousePos == null) {
     562            candidateNode = null;
     563            candidateSegment = null;
     564            return;
     565        }
     566
     567        if (state == State.selecting) {
     568            targetWay = IWATargetWayHelper.findWay(mv, mousePos);
     569        } else if (state == State.improving) {
     570            if (ctrl && !alt) {
     571                candidateSegment = IWATargetWayHelper.findCandidateSegment(mv,
     572                        targetWay, mousePos);
     573                candidateNode = null;
     574            } else {
     575                candidateNode = IWATargetWayHelper.findCandidateNode(mv,
     576                        targetWay, mousePos);
     577                candidateSegment = null;
     578            }
     579        }
     580    }
     581
     582    /**
     583     * Switches to Selecting state
     584     */
     585    public void startSelecting() {
     586        state = State.selecting;
     587
     588        targetWay = null;
     589        if (getCurrentDataSet() != null) {
     590            getCurrentDataSet().clearSelection();
     591        }
     592
     593        mv.repaint();
     594        updateStatusLine();
     595    }
     596
     597    /**
     598     * Switches to Improving state
     599     *
     600     * @param targetWay Way that is going to be improved
     601     */
     602    public void startImproving(Way targetWay) {
     603        state = State.improving;
     604
     605        List<OsmPrimitive> currentSelection = (List<OsmPrimitive>) getCurrentDataSet().getSelected();
     606        if (currentSelection.size() != 1
     607                || !currentSelection.get(0).equals(targetWay)) {
     608            selectionChangedBlocked = true;
     609            getCurrentDataSet().clearSelection();
     610            getCurrentDataSet().setSelected(targetWay.getPrimitiveId());
     611            selectionChangedBlocked = false;
     612        }
     613
     614        this.targetWay = targetWay;
     615        this.candidateNode = null;
     616        this.candidateSegment = null;
     617
     618        mv.repaint();
     619        updateStatusLine();
     620    }
     621
     622    /**
     623     * Updates the state according to the current selection. Goes to Improve
     624     * state if a single way or node is selected. Extracts a way by a node in
     625     * the second case.
     626     *
     627     */
     628    private void updateStateByCurrentSelection() {
     629        final ArrayList<Node> nodeList = new ArrayList<Node>();
     630        final ArrayList<Way> wayList = new ArrayList<Way>();
     631        final Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
     632
     633        // Collecting nodes and ways from the selection
     634        for (OsmPrimitive p : sel) {
     635            if (p instanceof Way) {
     636                wayList.add((Way) p);
     637            }
     638            if (p instanceof Node) {
     639                nodeList.add((Node) p);
     640            }
     641        }
     642
     643        if (wayList.size() == 1) {
     644            // Starting improving the single selected way
     645            startImproving(wayList.get(0));
     646            return;
     647        } else if (nodeList.size() > 0) {
     648            // Starting improving the only way of the single selected node
     649            if (nodeList.size() == 1) {
     650                List<OsmPrimitive> r = nodeList.get(0).getReferrers();
     651                if (r.size() == 1 && (r.get(0) instanceof Way)) {
     652                    startImproving((Way) r.get(0));
     653                    return;
     654                }
     655            }
     656        }
     657
     658        // Starting selecting by default
     659        startSelecting();
     660    }
     661}
  • src/org/openstreetmap/josm/gui/MapFrame.java

    diff --git a/src/org/openstreetmap/josm/gui/MapFrame.java b/src/org/openstreetmap/josm/gui/MapFrame.java
    index a0de622..6121eea 100644
    a b import org.openstreetmap.josm.Main; 
    4141import org.openstreetmap.josm.actions.mapmode.DeleteAction;
    4242import org.openstreetmap.josm.actions.mapmode.DrawAction;
    4343import org.openstreetmap.josm.actions.mapmode.ExtrudeAction;
     44import org.openstreetmap.josm.actions.mapmode.ImproveWayAccuracyAction;
    4445import org.openstreetmap.josm.actions.mapmode.MapMode;
    4546import org.openstreetmap.josm.actions.mapmode.ParallelWayAction;
    4647import org.openstreetmap.josm.actions.mapmode.SelectAction;
    public class MapFrame extends JPanel implements Destroyable, LayerChangeListener 
    147148        addMapMode(new IconToggleButton(new DeleteAction(this), !Main.pref.getBoolean("expert", false)));
    148149        addMapMode(new IconToggleButton(new ExtrudeAction(this), !Main.pref.getBoolean("expert", false)));
    149150        addMapMode(new IconToggleButton(new ParallelWayAction(this), !Main.pref.getBoolean("expert", false)));
     151        addMapMode(new IconToggleButton(new ImproveWayAccuracyAction(Main.map), !Main.pref.getBoolean("expert", false)));
    150152
    151153        toolGroup.setSelected(((AbstractButton)toolBarActions.getComponent(0)).getModel(), true);
    152154
  • src/org/openstreetmap/josm/plugins/PluginHandler.java

    diff --git a/src/org/openstreetmap/josm/plugins/PluginHandler.java b/src/org/openstreetmap/josm/plugins/PluginHandler.java
    index 342979e..da3b5d7 100644
    a b public class PluginHandler { 
    105105            new DeprecatedPlugin("wmsplugin", IN_CORE),
    106106            new DeprecatedPlugin("ParallelWay", IN_CORE),
    107107            new DeprecatedPlugin("dumbutils", tr("replaced by new {0} plugin","utilsplugin2")),
     108            new DeprecatedPlugin("ImproveWayAccuracy", IN_CORE),
    108109        });
    109110    }
    110111