Ticket #5500: latest.selectaction.multiple.files.patch

File latest.selectaction.multiple.files.patch, 86.1 KB (added by cmuelle8, 15 years ago)

fixes most issues, more documentation, more cleanup done, please test thoroughly

  • src/org/openstreetmap/josm/actions/mapmode/ZoomAction.java

     
    55
    66import java.awt.Rectangle;
    77import java.awt.event.KeyEvent;
     8import java.awt.event.MouseEvent;
    89
    910import org.openstreetmap.josm.Main;
    1011import org.openstreetmap.josm.gui.MapFrame;
     
    4950    /**
    5051     * Zoom to the rectangle on the map.
    5152     */
    52     public void selectionEnded(Rectangle r, boolean alt, boolean shift, boolean ctrl) {
     53    public void selectionEnded(Rectangle r, MouseEvent e) {
    5354        if (r.width >= 3 && r.height >= 3 && Main.isDisplayingMapView()) {
    5455            MapView mv = Main.map.mapView;
    5556            mv.zoomToFactor(mv.getEastNorth(r.x+r.width/2, r.y+r.height/2), r.getWidth()/mv.getWidth());
  • src/org/openstreetmap/josm/actions/mapmode/SelectAction.java

     
    1111import java.awt.event.InputEvent;
    1212import java.awt.event.KeyEvent;
    1313import java.awt.event.MouseEvent;
    14 import java.util.ArrayList;
     14import java.awt.geom.Point2D;
    1515import java.util.Collection;
    1616import java.util.Collections;
    17 import java.util.HashSet;
    1817import java.util.Iterator;
    1918import java.util.LinkedList;
    20 import java.util.Set;
    21 import java.util.TreeSet;
    2219
    2320import javax.swing.JOptionPane;
    2421
     
    4643import org.openstreetmap.josm.gui.layer.Layer;
    4744import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    4845import org.openstreetmap.josm.tools.ImageProvider;
     46import org.openstreetmap.josm.tools.Pair;
    4947import org.openstreetmap.josm.tools.PlatformHookOsx;
    5048import org.openstreetmap.josm.tools.Shortcut;
    5149
     
    6462
    6563    enum Mode { move, rotate, select }
    6664    private Mode mode = null;
    67     private long mouseDownTime = 0;
    68     private boolean didMove = false;
     65    private SelectionManager selectionManager;
     66
    6967    private boolean cancelDrawMode = false;
    70     private Node virtualNode = null;
    71     private Collection<WaySegment> virtualWays = new ArrayList<WaySegment>();
     68    private boolean didMouseDrag = false;
     69
     70    /**
     71     * The component this SelectAction is associated with.
     72     */
     73    private final MapView mv;
    7274
    7375    /**
    7476     * The old cursor before the user pressed the mouse button.
    7577     */
    7678    private Cursor oldCursor;
     79
    7780    /**
    7881     * The position of the mouse before the user moves a node.
    7982     */
    8083    private Point mousePos;
    81     private SelectionManager selectionManager;
     84
     85    /**
     86     * The time of the user mouse down event.
     87     */
     88    private long mouseDownTime = 0;
    8289
    8390    /**
    8491     * The time which needs to pass between click and release before something
     
    9299     */
    93100    private int initialMoveThreshold;
    94101    private boolean initialMoveThresholdExceeded = false;
     102
    95103    /**
    96104     * Create a new SelectAction
    97105     * @param mapFrame The MapFrame this action belongs to.
     
    101109                Shortcut.registerShortcut("mapmode:select", tr("Mode: {0}", tr("Select")), KeyEvent.VK_S, Shortcut.GROUP_EDIT),
    102110                mapFrame,
    103111                getCursor("normal", "selection", Cursor.DEFAULT_CURSOR));
     112        mv = mapFrame.mapView;
    104113        putValue("help", "Action/Move/Move");
    105         selectionManager = new SelectionManager(this, false, mapFrame.mapView);
     114        selectionManager = new SelectionManager(this, false, mv);
    106115        initialMoveDelay = Main.pref.getInteger("edit.initial-move-delay",200);
    107116        initialMoveThreshold = Main.pref.getInteger("edit.initial-move-threshold",5);
    108117    }
     
    117126
    118127    private void setCursor(Cursor c) {
    119128        if (oldCursor == null) {
    120             oldCursor = Main.map.mapView.getCursor();
    121             Main.map.mapView.setCursor(c);
     129            oldCursor = mv.getCursor();
     130            mv.setCursor(c);
    122131        }
    123132    }
    124133
    125134    private void restoreCursor() {
    126135        if (oldCursor != null) {
    127             Main.map.mapView.setCursor(oldCursor);
     136            mv.setCursor(oldCursor);
    128137            oldCursor = null;
    129138        }
    130139    }
    131140
    132141    @Override public void enterMode() {
    133142        super.enterMode();
    134         Main.map.mapView.addMouseListener(this);
    135         Main.map.mapView.addMouseMotionListener(this);
    136         Main.map.mapView.setVirtualNodesEnabled(
     143        mv.addMouseListener(this);
     144        mv.addMouseMotionListener(this);
     145        mv.setVirtualNodesEnabled(
    137146                Main.pref.getInteger("mappaint.node.virtual-size", 8) != 0);
    138147    }
    139148
    140149    @Override public void exitMode() {
    141150        super.exitMode();
    142         selectionManager.unregister(Main.map.mapView);
    143         Main.map.mapView.removeMouseListener(this);
    144         Main.map.mapView.removeMouseMotionListener(this);
    145         Main.map.mapView.setVirtualNodesEnabled(false);
     151        selectionManager.unregister(mv);
     152        mv.removeMouseListener(this);
     153        mv.removeMouseMotionListener(this);
     154        mv.setVirtualNodesEnabled(false);
    146155    }
    147156
    148157    /**
     
    151160     * mouse (which will become selected).
    152161     */
    153162    @Override public void mouseDragged(MouseEvent e) {
    154         if(!Main.map.mapView.isActiveLayerVisible())
     163        if(!mv.isActiveLayerVisible())
    155164            return;
    156165
    157166        cancelDrawMode = true;
     
    168177            setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    169178        }
    170179
    171         if (mousePos == null) {
    172             mousePos = e.getPoint();
    173             return;
    174         }
    175 
    176180        if (!initialMoveThresholdExceeded) {
    177181            int dxp = mousePos.x - e.getX();
    178182            int dyp = mousePos.y - e.getY();
     
    181185            initialMoveThresholdExceeded = true;
    182186        }
    183187
    184         EastNorth mouseEN = Main.map.mapView.getEastNorth(e.getX(), e.getY());
    185         EastNorth mouseStartEN = Main.map.mapView.getEastNorth(mousePos.x, mousePos.y);
     188        EastNorth mouseEN = mv.getEastNorth(e.getX(), e.getY());
     189        EastNorth mouseStartEN = mv.getEastNorth(mousePos.x, mousePos.y);
    186190        double dx = mouseEN.east() - mouseStartEN.east();
    187191        double dy = mouseEN.north() - mouseStartEN.north();
    188192        if (dx == 0 && dy == 0)
     
    254258            }
    255259        }
    256260
    257         Main.map.mapView.repaint();
     261        mv.repaint();
    258262        mousePos = e.getPoint();
    259263
    260         didMove = true;
     264        didMouseDrag = true;
    261265    }
    262266
    263267    @Override public void mouseMoved(MouseEvent e) {
     
    268272        }
    269273    }
    270274
    271     private Collection<OsmPrimitive> getNearestCollectionVirtual(Point p) {
    272         int snapDistance = Main.pref.getInteger("mappaint.node.virtual-snap-distance", 8);
    273         int virtualSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
    274         snapDistance *= snapDistance;
    275 
    276         MapView c = Main.map.mapView;
    277         Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
     275    private Node virtualNode = null;
     276    private Collection<WaySegment> virtualWays = new LinkedList<WaySegment>();
    278277
    279         // take nearest node
    280         OsmPrimitive osm = c.getNearestNode(p, OsmPrimitive.isSelectablePredicate);
     278    /**
     279     * Calculate a virtual node if there is enough visual space to draw a crosshair
     280     * node and the middle of a way segment is clicked.  If the user drags the
     281     * crosshair node, it will be added to all ways in <code>virtualWays</code>.
     282     *
     283     * @param e contains the point clicked
     284     * @return whether <code>virtualNode</code> and <code>virtualWays</code> were setup.
     285     */
     286    private boolean setupVirtual(MouseEvent e) {
     287        if (Main.pref.getInteger("mappaint.node.virtual-size", 8) > 0) {
     288            int virtualSnapDistSq = Main.pref.getInteger("mappaint.node.virtual-snap-distance", 8);
     289            int virtualSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70);
     290            virtualSnapDistSq *= virtualSnapDistSq;
    281291
    282         if (osm != null) {
    283             for (Node n : c.getNearestNodes(p, OsmPrimitive.isSelectablePredicate)) {
    284                 if (sel.contains(n)) {
    285                     // take nearest selected node
    286                     osm = n;
    287                     break;
    288                 }
    289             }
    290         } else {
    291             Node virtualWayNode = null;
     292            Collection<WaySegment> selVirtualWays = new LinkedList<WaySegment>();
     293            Pair<Node, Node> vnp = null, wnp = new Pair<Node, Node>(null, null);
     294            Point p = e.getPoint();
    292295            Way w = null;
    293296
    294             Collection<WaySegment> virtualWaysInSel = new ArrayList<WaySegment>();
    295             Collection<WaySegment> wss = c.getNearestWaySegments(p, OsmPrimitive.isSelectablePredicate);
    296             for(WaySegment nearestWS : wss) {
    297                 if (nearestWS == null) {
    298                     continue;
    299                 }
    300 
    301                 w = nearestWS.way;
    302                 if (osm == null && sel.contains(w)) {
    303                     // take nearest selected way
    304                     osm = w;
    305                 }
     297            for(WaySegment ws : mv.getNearestWaySegments(p, OsmPrimitive.isSelectablePredicate)) {
     298                w = ws.way;
    306299
    307                 if (Main.pref.getInteger("mappaint.node.virtual-size", 8) > 0) {
    308                     Point p1 = c.getPoint(w.getNode(nearestWS.lowerIndex));
    309                     Point p2 = c.getPoint(w.getNode(nearestWS.lowerIndex+1));
    310                     if(SimplePaintVisitor.isLargeSegment(p1, p2, virtualSpace))
     300                Point2D p1 = mv.getPoint2D(wnp.a = w.getNode(ws.lowerIndex));
     301                Point2D p2 = mv.getPoint2D(wnp.b = w.getNode(ws.lowerIndex+1));
     302                if(SimplePaintVisitor.isLargeSegment(p1, p2, virtualSpace))
     303                {
     304                    Point2D pc = new Point2D.Double((p1.getX()+p2.getX())/2, (p1.getY()+p2.getY())/2);
     305                    if (p.distanceSq(pc) < virtualSnapDistSq)
    311306                    {
    312                         Point pc = new Point((p1.x+p2.x)/2, (p1.y+p2.y)/2);
    313                         if (p.distanceSq(pc) < snapDistance)
    314                         {
    315                             // Check that only segments on top of each other get added to the
    316                             // virtual ways list. Otherwise ways that coincidentally have their
    317                             // virtual node at the same spot will be joined which is likely unwanted
    318                             if(virtualWayNode != null) {
    319                                 if(!w.getNode(nearestWS.lowerIndex+1).equals(virtualWayNode)
    320                                         && !w.getNode(nearestWS.lowerIndex).equals(virtualWayNode)) {
    321                                     continue;
    322                                 }
    323                             } else {
    324                                 virtualWayNode = w.getNode(nearestWS.lowerIndex+1);
    325                             }
    326 
    327                             (!sel.contains(w) ? virtualWays : virtualWaysInSel).add(nearestWS);
    328                             if(virtualNode == null) {
    329                                 virtualNode = new Node(Main.map.mapView.getLatLon(pc.x, pc.y));
    330                             }
     307                        // Check that only segments on top of each other get added to the
     308                        // virtual ways list. Otherwise ways that coincidentally have their
     309                        // virtual node at the same spot will be joined which is likely unwanted
     310                        Pair.sort(wnp);
     311                        if (vnp == null) {
     312                            vnp = new Pair<Node, Node>(wnp.a, wnp.b);
     313                            virtualNode = new Node(mv.getLatLon(pc.getX(), pc.getY()));
     314                        }
     315                        if (vnp.equals(wnp)) {
     316                            (w.isSelected() ? selVirtualWays : virtualWays).add(ws);
    331317                        }
    332318                    }
    333319                }
    334320            }
    335321
    336             if (virtualNode != null) {
    337                 // insert virtualNode into all segments if nothing was selected,
    338                 // else only into the (previously) selected segments
    339                 virtualWays = virtualWaysInSel.isEmpty() ? virtualWays : virtualWaysInSel;
     322            if (!selVirtualWays.isEmpty()) {
     323                virtualWays = selVirtualWays;
    340324            }
     325        }
    341326
    342             if (osm == null && !wss.isEmpty()) {
    343                 // take nearest way
    344                 osm = wss.iterator().next().way;
     327        return !virtualWays.isEmpty();
     328    }
     329
     330    private Collection<OsmPrimitive> cycleList = Collections.emptyList();
     331    private boolean cyclePrims = false;
     332    private OsmPrimitive cycleStart = null;
     333
     334    /**
     335     *
     336     * @param osm nearest primitive found by simple method
     337     * @param e
     338     * @return
     339     */
     340    private Collection<OsmPrimitive> cycleSetup(Collection<OsmPrimitive> single, MouseEvent e) {
     341        OsmPrimitive osm = null;
     342
     343        if (single == null) {
     344            single = MapView.asColl(
     345                    mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, false));
     346        }
     347
     348        if (!single.isEmpty()) {
     349            boolean waitForMouseUp = Main.pref.getBoolean("mappaint.select.waits-for-mouse-up", false);
     350            boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
     351            boolean alt = ((e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0
     352                    || Main.pref.getBoolean("selectaction.cycles.multiple.matches", false));
     353
     354            Point p = e.getPoint();
     355            osm = single.iterator().next();
     356
     357            if (!alt) {
     358                cycleList = MapView.asColl(osm);
     359                if (waitForMouseUp) {
     360                    // find a selected nearest node or way, not the true nearest..
     361                    osm = mv.getNearestNodeOrWay(p, OsmPrimitive.isSelectablePredicate, true);
     362                }
     363            } else {
     364                if (osm instanceof Node) {
     365                    cycleList = new LinkedList<OsmPrimitive>(mv.getNearestNodes(p, OsmPrimitive.isSelectablePredicate));
     366                } else if (osm instanceof Way) {
     367                    cycleList = new LinkedList<OsmPrimitive>(mv.getNearestWays(p, OsmPrimitive.isSelectablePredicate));
     368                }
     369
     370                if (!waitForMouseUp && cycleList.size()>1) {
     371                    cyclePrims = false;
     372
     373                    OsmPrimitive old = osm;
     374                    for (OsmPrimitive o : cycleList) {
     375                        if (o.isSelected()) {
     376                            cyclePrims = true;
     377                            osm = o;
     378                            break;
     379                        }
     380                    }
     381
     382                    // for cycle groups of 2, we can toggle to the true nearest
     383                    // primitive on mouse presses, if it is not selected and if ctrl is not used
     384                    // else, if rotation is possible, defer sel change to mouse release
     385                    if (cycleList.size()==2) {
     386                        if (!(old.equals(osm) || ctrl)) {
     387                            cyclePrims = false;
     388                            osm = old;
     389                        }
     390                    }
     391                }
    345392            }
    346393        }
    347394
    348         if (osm == null)
    349             return Collections.emptySet();
    350         return Collections.singleton(osm);
     395        return MapView.asColl(osm);
    351396    }
    352397
    353398    /**
     
    360405     * cursor to movement.
    361406     */
    362407    @Override public void mousePressed(MouseEvent e) {
    363         if(!Main.map.mapView.isActiveLayerVisible())
     408        debug("mousePressed: e.getPoint()=" + e.getPoint());
     409
     410        // return early
     411        if(!mv.isActiveLayerVisible()
     412                || !(Boolean)this.getValue("active")
     413                || e.getButton() != MouseEvent.BUTTON1)
    364414            return;
    365415
    366416        // request focus in order to enable the expected keyboard shortcuts
    367         Main.map.mapView.requestFocus();
     417        mv.requestFocus();
    368418
    369         cancelDrawMode = false;
    370         if (! (Boolean)this.getValue("active")) return;
    371         if (e.getButton() != MouseEvent.BUTTON1)
    372             return;
    373419        boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    374420        boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
    375421
    376422        // We don't want to change to draw tool if the user tries to (de)select
    377423        // stuff but accidentally clicks in an empty area when selection is empty
    378         if(shift || ctrl) {
    379             cancelDrawMode = true;
    380         }
    381 
    382         mouseDownTime = System.currentTimeMillis();
    383         didMove = false;
     424        cancelDrawMode = (shift || ctrl);
     425        didMouseDrag = false;
    384426        initialMoveThresholdExceeded = false;
     427        mouseDownTime = System.currentTimeMillis();
     428        mousePos = e.getPoint();
     429
     430        Collection<OsmPrimitive> c = MapView.asColl(
     431                mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, false));
    385432
    386         Collection<OsmPrimitive> osmColl = getNearestCollectionVirtual(e.getPoint());
     433        if (shift && ctrl) {
     434            mode = Mode.rotate;
    387435
    388         if (ctrl && shift) {
    389436            if (getCurrentDataSet().getSelected().isEmpty()) {
    390                 selectPrims(osmColl, true, false, false, false);
     437                getCurrentDataSet().setSelected(c);
    391438            }
    392             mode = Mode.rotate;
     439
     440            // Mode.select redraws when selectPrims is called
     441            // Mode.move   redraws when mouseDragged is called
     442            // Mode.rotate redraws here
    393443            setCursor(ImageProvider.getCursor("rotate", null));
    394         } else if (!osmColl.isEmpty()) {
    395             // Don't replace the selection now if the user clicked on a
    396             // selected object (this would break moving of selected groups).
    397             // We'll do that later in mouseReleased if the user didn't try to
    398             // move.
    399             selectPrims(osmColl,
    400                     shift || getCurrentDataSet().getSelected().containsAll(osmColl),
    401                     ctrl, false, false);
     444            mv.repaint();
     445        } else if (!c.isEmpty()) {
    402446            mode = Mode.move;
     447
     448            if (!cancelDrawMode && c.iterator().next() instanceof Way) {
     449                setupVirtual(e);
     450            }
     451
     452            selectPrims(cycleSetup(c, e), e, false, false);
    403453        } else {
    404454            mode = Mode.select;
    405             oldCursor = Main.map.mapView.getCursor();
    406             selectionManager.register(Main.map.mapView);
    407             selectionManager.mousePressed(e);
    408         }
    409455
    410         if(mode != Mode.move || shift || ctrl) {
    411             virtualNode = null;
    412             virtualWays.clear();
     456            oldCursor = mv.getCursor();
     457            selectionManager.register(mv);
     458            selectionManager.mousePressed(e);
    413459        }
    414460
    415461        updateStatusLine();
    416         // Mode.select redraws when selectPrims is called
    417         // Mode.move   redraws when mouseDragged is called
    418         // Mode.rotate redraws here
    419         if(mode == Mode.rotate) {
    420             Main.map.mapView.repaint();
    421         }
    422 
    423         mousePos = e.getPoint();
    424462    }
    425463
    426     /**
    427      * Restore the old mouse cursor.
    428      */
    429     @Override public void mouseReleased(MouseEvent e) {
    430         if(!Main.map.mapView.isActiveLayerVisible())
     464    @Override
     465    public void mouseReleased(MouseEvent e) {
     466        debug("mouseReleased: e.getPoint()=" + e.getPoint());
     467
     468        if(!mv.isActiveLayerVisible())
    431469            return;
    432470
     471        restoreCursor();
    433472        if (mode == Mode.select) {
    434             selectionManager.unregister(Main.map.mapView);
     473            selectionManager.unregister(mv);
    435474
    436475            // Select Draw Tool if no selection has been made
    437476            if(getCurrentDataSet().getSelected().size() == 0 && !cancelDrawMode) {
     
    439478                return;
    440479            }
    441480        }
    442         restoreCursor();
    443481
    444482        if (mode == Mode.move) {
    445             boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    446             boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
    447             boolean alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0
    448                             || Main.pref.getBoolean("selectaction.cycles.multiple.matches", false);
     483            if (!didMouseDrag) {
     484                // only built in move mode
     485                virtualWays.clear();
     486                virtualNode = null;
    449487
    450             virtualWays.clear();
    451             virtualNode = null;
     488                // do nothing if the click was to short to be recognized as a drag,
     489                // but the release position is farther than 10px away from the press position
     490                if (mousePos.distanceSq(e.getPoint())<100) {
     491                    selectPrims(cyclePrims(cycleList, e), e, true, false);
    452492
    453             if (!didMove) {
    454                 Collection<OsmPrimitive> c = Main.map.mapView.getNearestCollection(e.getPoint(), OsmPrimitive.isSelectablePredicate);
    455                 if (!c.isEmpty() && alt) {
    456                     if (c.iterator().next() instanceof Node) {
    457                         // consider all nearest nodes
    458                         c = new ArrayList<OsmPrimitive>(Main.map.mapView.getNearestNodes(e.getPoint(), OsmPrimitive.isSelectablePredicate));
    459                     } else {
    460                         // consider all nearest primitives (should be only ways at this point..)
    461                         c = Main.map.mapView.getAllNearest(e.getPoint(), OsmPrimitive.isSelectablePredicate);
     493                    // If the user double-clicked a node, change to draw mode
     494                    Collection<OsmPrimitive> c = getCurrentDataSet().getSelected();
     495                    if(e.getClickCount() >=2 && c.size() == 1 && c.iterator().next() instanceof Node) {
     496                        // We need to do it like this as otherwise drawAction will see a double
     497                        // click and switch back to SelectMode
     498                        Main.worker.execute(new Runnable(){
     499                            public void run() {
     500                                Main.map.selectDrawTool(true);
     501                            }
     502                        });
     503                        return;
    462504                    }
    463505                }
    464                 selectPrims(c, shift, ctrl, true, false);
    465 
    466                 // If the user double-clicked a node, change to draw mode
    467                 c = getCurrentDataSet().getSelected();
    468                 if(e.getClickCount() >=2 && c.size() == 1 && c.iterator().next() instanceof Node) {
    469                     // We need to do it like this as otherwise drawAction will see a double
    470                     // click and switch back to SelectMode
    471                     Main.worker.execute(new Runnable(){
    472                         public void run() {
    473                             Main.map.selectDrawTool(true);
    474                         }
    475                     });
    476                     return;
    477                 }
    478506            } else {
    479                 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
    480                 Collection<OsmPrimitive> s = new TreeSet<OsmPrimitive>();
    481                 int max = Main.pref.getInteger("warn.move.maxelements", 20);
    482                 for (OsmPrimitive osm : selection)
    483                 {
    484                     if(osm instanceof Node) {
    485                         s.add(osm);
    486                     } else if(osm instanceof Way)
    487                     {
    488                         s.add(osm);
    489                         s.addAll(((Way)osm).getNodes());
     507                int max = Main.pref.getInteger("warn.move.maxelements", 20), limit = max;
     508                for (OsmPrimitive osm : getCurrentDataSet().getSelected()) {
     509                    if (osm instanceof Way) {
     510                        limit -= ((Way)osm).getNodes().size();
    490511                    }
    491                     if(s.size() > max)
    492                     {
    493                         ExtendedDialog ed = new ExtendedDialog(
    494                                 Main.parent,
    495                                 tr("Move elements"),
    496                                 new String[] {tr("Move them"), tr("Undo move")});
    497                         ed.setButtonIcons(new String[] {"reorder.png", "cancel.png"});
    498                         ed.setContent(tr("You moved more than {0} elements. "
    499                                 + "Moving a large number of elements is often an error.\n"
    500                                 + "Really move them?", max));
    501                         ed.setCancelButton(2);
    502                         ed.toggleEnable("movedManyElements");
    503                         ed.showDialog();
    504 
    505                         if(ed.getValue() != 1)
    506                         {
    507                             Main.main.undoRedo.undo();
    508                         }
     512                    if ((limit -= 1) < 0) {
    509513                        break;
    510514                    }
    511515                }
    512                 if (ctrl) {
    513                     Collection<Node> affectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
    514                     Collection<Node> nn = Main.map.mapView.getNearestNodes(e.getPoint(), affectedNodes, OsmPrimitive.isSelectablePredicate);
    515                     if (nn != null) {
    516                         Node targetNode = nn.iterator().next();
    517                         Set<Node> nodesToMerge = new HashSet<Node>(affectedNodes);
    518                         nodesToMerge.add(targetNode);
    519                         if (!nodesToMerge.isEmpty()) {
    520                             Command cmd = MergeNodesAction.mergeNodes(Main.main.getEditLayer(),nodesToMerge, targetNode);
    521                             if(cmd != null) {
    522                                 Main.main.undoRedo.add(cmd);
    523                             }
    524                         }
     516                if (limit < 0) {
     517                    ExtendedDialog ed = new ExtendedDialog(
     518                            Main.parent,
     519                            tr("Move elements"),
     520                            new String[] {tr("Move them"), tr("Undo move")});
     521                    ed.setButtonIcons(new String[] {"reorder.png", "cancel.png"});
     522                    ed.setContent(tr("You moved more than {0} elements. "
     523                            + "Moving a large number of elements is often an error.\n"
     524                            + "Really move them?", max));
     525                    ed.setCancelButton(2);
     526                    ed.toggleEnable("movedManyElements");
     527                    ed.showDialog();
     528
     529                    if(ed.getValue() != 1) {
     530                        Main.main.undoRedo.undo();
    525531                    }
     532                } else {
     533                    mergePrims(getCurrentDataSet().getSelectedNodes(), e);
    526534                }
    527535                getCurrentDataSet().fireSelectionChanged();
    528536            }
     
    534542        updateStatusLine();
    535543    }
    536544
    537     public void selectionEnded(Rectangle r, boolean alt, boolean shift, boolean ctrl) {
    538         selectPrims(selectionManager.getObjectsInRectangle(r, alt), shift, ctrl, true, true);
     545    public void selectionEnded(Rectangle r, MouseEvent e) {
     546        boolean alt = (e.getModifiersEx() & (MouseEvent.ALT_DOWN_MASK | MouseEvent.ALT_GRAPH_DOWN_MASK)) != 0;
     547        selectPrims(selectionManager.getObjectsInRectangle(r, alt), e, true, true);
    539548    }
    540549
    541     private boolean selMorePrims = false;
    542     private OsmPrimitive selCycleStart = null;
    543 
    544     public void selectPrims(Collection<OsmPrimitive> selectionList, boolean shift,
    545             boolean ctrl, boolean released, boolean area) {
    546         DataSet ds = getCurrentDataSet();
    547 
    548         // decides on mousePressed whether
    549         //      to cycle on mouseReleased (selList already selected)
    550         //      or not                    (selList is a new selection)
    551         selMorePrims = (released || area) ? selMorePrims : ds.getSelected().containsAll(selectionList);
     550    /**
     551     * Modifies current selection state and returns the next element in a
     552     * selection cycle given by <code>prims</code>.
     553     * @param prims the primitives that form the selection cycle
     554     * @param shift whether shift is pressed
     555     * @param ctrl whether ctrl is pressed
     556     * @return the next element of cycle list <code>prims</code>.
     557     */
     558    private Collection<OsmPrimitive> cyclePrims(Collection<OsmPrimitive> prims, MouseEvent e) {
     559        OsmPrimitive nxt = null;
    552560
    553         // not allowed together: do not change dataset selection, return early
    554         if ((shift && ctrl) || (ctrl && !released) || (!virtualWays.isEmpty()))
    555             return;
     561        debug("cyclePrims(): entry.....");
     562        for (OsmPrimitive osm : prims) {
     563            debug("cyclePrims(): prims id=" + osm.getId());
     564        }
    556565
    557         // toggle through possible objects on mouse release
    558         if (released && !area) {
    559             if (selectionList.size() > 1) {
    560                 Collection<OsmPrimitive> coll = ds.getSelected();
     566        if (prims.size() > 1) {
     567            boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
     568            boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
    561569
    562                 OsmPrimitive first, foundInDS, node, nxt;
    563                 first = nxt = selectionList.iterator().next();
    564                 foundInDS = node = null;
     570            DataSet ds = getCurrentDataSet();
     571            OsmPrimitive first = prims.iterator().next(), foundInDS = null;
     572            nxt = first;
    565573
    566                 for (Iterator<OsmPrimitive> i = selectionList.iterator(); i.hasNext(); ) {
    567                     if (selMorePrims && shift) {
    568                         if (!coll.contains(nxt = i.next())) {
    569                             break; // take first primitive not in dsSel or last if all contained
    570                         }
    571                     } else {
    572                         if (coll.contains(nxt = i.next())) {
    573                             foundInDS = nxt;
    574                             if (selMorePrims || ctrl) {
    575                                 ds.clearSelection(nxt);
    576                                 nxt = i.hasNext() ? i.next() : first;
    577                             }
    578                             break; // take next primitive of selList
    579                         } else if (nxt instanceof Node && node == null) {
    580                             node = nxt;
     574            for (Iterator<OsmPrimitive> i = prims.iterator(); i.hasNext(); ) {
     575                if (cyclePrims && shift) {
     576                    if (!(nxt = i.next()).isSelected()) {
     577                        debug("cyclePrims(): taking " + nxt.getId());
     578                        break; // take first primitive in prims list not in sel
     579                    }
     580                } else {
     581                    if ((nxt = i.next()).isSelected()) {
     582                        foundInDS = nxt;
     583                        if (cyclePrims || ctrl) {
     584                            ds.clearSelection(foundInDS);
     585                            nxt = i.hasNext() ? i.next() : first;
    581586                        }
     587                        debug("selectPrims(): taking " + nxt.getId());
     588                        break; // take next primitive in prims list
    582589                    }
    583590                }
     591            }
    584592
    585                 if (ctrl) {
    586                     if (foundInDS != null) {
    587                         // a member of selList was foundInDS
    588                         if (!selectionList.contains(selCycleStart)) {
    589                             selCycleStart = foundInDS;
    590                         }
    591                         // check if selCycleStart == prim (equals next(foundInDS))
    592                         if (selCycleStart.equals(nxt)) {
    593                             ds.addSelected(nxt);   // cycle complete, prim toggled below
    594                             selCycleStart = null;  // check: might do w/out ??
    595                         }
    596                     } else {
    597                         // no member of selList was foundInDS (sets were disjunct), setup for new cycle
    598                         selCycleStart = nxt = (node != null) ? node : first;
     593            if (ctrl) {
     594                // a member of prims was found in the current dataset selection
     595                if (foundInDS != null) {
     596                    // mouse was moved to a different selection group w/ a previous sel
     597                    if (!prims.contains(cycleStart)) {
     598                        ds.clearSelection(prims);
     599                        cycleStart = foundInDS;
     600                        debug("selectPrims(): cycleStart set to foundInDS=" + cycleStart.getId());
     601                    } else if (cycleStart.equals(nxt)) {
     602                        // loop detected, insert deselect step
     603                        ds.addSelected(nxt);
     604                        debug("selectPrims(): cycleStart hit");
    599605                    }
     606                } else {
     607                    // setup for iterating a sel group again or a new, different one..
     608                    nxt = (prims.contains(cycleStart)) ? cycleStart : first;
     609                    cycleStart = nxt;
     610                    debug("selectPrims(): cycleStart set to nxt=" + cycleStart.getId());
    600611                }
     612            } else {
     613                cycleStart = null;
     614            }
     615
     616            debug("cyclePrims(): truncated prims list to id=" + nxt.getId());
     617        }
     618
     619        // pass on prims, if it had less than 2 elements
     620        return (nxt != null) ? MapView.asColl(nxt) : prims;
     621    }
     622
     623    private void mergePrims(Collection<Node> affectedNodes, MouseEvent e) {
     624        boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    601625
    602                 selectionList = new ArrayList<OsmPrimitive>(1); // do not modify the passed object..
    603                 selectionList.add(nxt);
     626        if (ctrl && !affectedNodes.isEmpty()) {
     627            Collection<Node> target = mv.getNearestNodes(e.getPoint(), affectedNodes, OsmPrimitive.isSelectablePredicate);
     628            if (!target.isEmpty()) {
     629                Collection<Node> nodesToMerge = new LinkedList<Node>(affectedNodes);
     630                nodesToMerge.add(target.iterator().next());
     631
     632                Command cmd = MergeNodesAction.mergeNodes(Main.main.getEditLayer(), nodesToMerge, target.iterator().next());
     633                if(cmd != null) {
     634                    Main.main.undoRedo.add(cmd);
     635                }
    604636            }
    605637        }
     638    }
     639
     640    private void selectPrims(Collection<OsmPrimitive> prims, MouseEvent e, boolean released, boolean area) {
     641        boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
     642        boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     643        DataSet ds = getCurrentDataSet();
     644
     645        // not allowed together: do not change dataset selection, return early
     646        if ((shift && ctrl) || (ctrl && !released) || (!virtualWays.isEmpty()))
     647            return;
     648
     649        if (!released) {
     650            // Don't replace the selection if the user clicked on a
     651            // selected object (it breaks moving of selected groups).
     652            // Do it later, on mouse release.
     653            shift |= getCurrentDataSet().getSelected().containsAll(prims);
     654        }
    606655
    607656        if (ctrl) {
    608657            // Ctrl on an item toggles its selection status,
    609658            // but Ctrl on an *area* just clears those items
    610659            // out of the selection.
    611660            if (area) {
    612                 ds.clearSelection(selectionList);
     661                ds.clearSelection(prims);
    613662            } else {
    614                 ds.toggleSelected(selectionList);
     663                ds.toggleSelected(prims);
    615664            }
     665        } else if (shift) {
     666            // add prims to an existing selection
     667            ds.addSelected(prims);
    616668        } else {
    617             // plain clicks with no modifiers
    618             if (!shift) {
    619                 ds.setSelected(selectionList);
    620             } else {
    621                 // add things to an
    622                 // existing selection.
    623                 ds.addSelected(selectionList);
    624             }
     669            // clear selection, then select the prims clicked
     670            ds.setSelected(prims);
    625671        }
    626672    }
    627673
     
    639685    @Override public boolean layerIsSupported(Layer l) {
    640686        return l instanceof OsmDataLayer;
    641687    }
     688
     689    private static void debug(String s) {
     690        //System.err.println("SelectAction:" + s);
     691    }
    642692}
  • src/org/openstreetmap/josm/data/osm/visitor/paint/SimplePaintVisitor.java

     
    1212import java.awt.RenderingHints;
    1313import java.awt.Stroke;
    1414import java.awt.geom.GeneralPath;
     15import java.awt.geom.Point2D;
    1516import java.util.Collection;
    1617import java.util.Iterator;
    1718
     
    2526import org.openstreetmap.josm.data.osm.RelationMember;
    2627import org.openstreetmap.josm.data.osm.Way;
    2728import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
    28 import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
    2929import org.openstreetmap.josm.gui.NavigatableComponent;
    3030
    3131/**
     
    265265            }
    266266
    267267            final int size = max((ds.isSelected(n) ? selectedNodeSize : 0),
    268                                     (n.isTagged() ? taggedNodeSize : 0),
    269                                     (n.isConnectionNode() ? connectionNodeSize : 0),
    270                                     unselectedNodeSize);
     268                    (n.isTagged() ? taggedNodeSize : 0),
     269                    (n.isConnectionNode() ? connectionNodeSize : 0),
     270                    unselectedNodeSize);
    271271
    272272            final boolean fill = (ds.isSelected(n) && fillSelectedNode) ||
    273                                     (n.isTagged() && fillTaggedNode) ||
    274                                     (n.isConnectionNode() && fillConnectionNode) ||
    275                                     fillUnselectedNode;
     273            (n.isTagged() && fillTaggedNode) ||
     274            (n.isConnectionNode() && fillConnectionNode) ||
     275            fillUnselectedNode;
    276276
    277277            drawNode(n, color, size, fill);
    278278        }
    279279    }
    280280
    281     public static boolean isLargeSegment(Point p1, Point p2, int space)
     281    public static boolean isLargeSegment(Point2D p1, Point2D p2, int space)
    282282    {
    283         int xd = p1.x-p2.x; if(xd < 0) {
    284             xd = -xd;
    285         }
    286         int yd = p1.y-p2.y; if(yd < 0) {
    287             yd = -yd;
    288         }
     283        double xd = Math.abs(p1.getX()-p2.getX());
     284        double yd = Math.abs(p1.getY()-p2.getY());
    289285        return (xd+yd > space);
    290286    }
    291287
  • src/org/openstreetmap/josm/gui/SelectionManager.java

     
    6060         * @param ctrl Whether the ctrl key was pressed
    6161         * @see InputEvent#getModifiersEx()
    6262         */
    63         public void selectionEnded(Rectangle r, boolean alt, boolean shift, boolean ctrl);
     63        public void selectionEnded(Rectangle r, MouseEvent e);
    6464        /**
    6565         * Called to register the selection manager for "active" property.
    6666         * @param listener The listener to register
     
    188188        mousePosStart = null;
    189189        mousePos = null;
    190190
    191         boolean shift = (e.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0;
    192         boolean alt = (e.getModifiersEx() & (MouseEvent.ALT_DOWN_MASK | MouseEvent.ALT_GRAPH_DOWN_MASK)) != 0;
    193         boolean ctrl = (e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0;
    194191        if ((e.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK) == 0) {
    195             selectionEndedListener.selectionEnded(r, alt, shift, ctrl);
     192            selectionEndedListener.selectionEnded(r, e);
    196193        }
    197194    }
    198195
     
    276273        Point center = new Point(r.x+r.width/2, r.y+r.height/2);
    277274
    278275        if (clicked) {
    279             OsmPrimitive osm = nc.getNearest(center, OsmPrimitive.isSelectablePredicate);
     276            OsmPrimitive osm = nc.getNearestNodeOrWay(center, OsmPrimitive.isSelectablePredicate);
    280277            if (osm != null) {
    281278                selection.add(osm);
    282279            }
  • src/org/openstreetmap/josm/gui/preferences/PrefJPanel.java

     
    194194        shortcutTable.getSelectionModel().addListSelectionListener(new cbAction(this));
    195195        //shortcutTable.setFillsViewportHeight(true); Java 1.6
    196196        shortcutTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
     197        shortcutTable.setAutoCreateRowSorter(true);
    197198        listScrollPane.setViewportView(shortcutTable);
    198199
    199200        listPane.add(listScrollPane);
  • src/org/openstreetmap/josm/gui/NavigatableComponent.java

     
    55
    66import java.awt.Point;
    77import java.awt.Rectangle;
     8import java.awt.geom.Point2D;
    89import java.util.ArrayList;
    910import java.util.Collection;
    1011import java.util.Collections;
     
    1516import java.util.List;
    1617import java.util.Locale;
    1718import java.util.Map;
     19import java.util.Set;
    1820import java.util.Stack;
    1921import java.util.TreeMap;
    2022import java.util.concurrent.CopyOnWriteArrayList;
     
    8486        }
    8587    }
    8688
    87     public static final int snapDistance = Main.pref.getInteger("node.snap-distance", 10);
    88     public static final int snapDistanceSq = sqr(snapDistance);
    89 
    90     private static int sqr(int a) { return a*a;}
    9189    /**
    9290     * The scale factor in x or y-units per pixel. This means, if scale = 10,
    9391     * every physical pixel on screen are 10 x or 10 y units in the
     
    193191        return getProjection().eastNorth2latlon(getEastNorth(x, y));
    194192    }
    195193
     194    public LatLon getLatLon(double x, double y) {
     195        return getLatLon((int)x, (int)y);
     196    }
     197
    196198    /**
    197199     * @param r
    198200     * @return Minimum bounds that will cover rectangle
     
    227229     * @return The point on screen where "point" would be drawn, relative
    228230     *      to the own top/left.
    229231     */
    230     public Point getPoint(EastNorth p) {
     232    public Point2D getPoint2D(EastNorth p) {
    231233        if (null == p)
    232234            return new Point();
    233235        double x = (p.east()-center.east())/scale + getWidth()/2;
    234236        double y = (center.north()-p.north())/scale + getHeight()/2;
    235         return new Point((int)x,(int)y);
     237        return new Point2D.Double(x, y);
    236238    }
    237239
    238     public Point getPoint(LatLon latlon) {
     240    public Point2D getPoint2D(LatLon latlon) {
    239241        if (latlon == null)
    240242            return new Point();
    241243        else if (latlon instanceof CachedLatLon)
    242             return getPoint(((CachedLatLon)latlon).getEastNorth());
     244            return getPoint2D(((CachedLatLon)latlon).getEastNorth());
    243245        else
    244             return getPoint(getProjection().latlon2eastNorth(latlon));
     246            return getPoint2D(getProjection().latlon2eastNorth(latlon));
     247    }
     248    public Point2D getPoint2D(Node n) {
     249        return getPoint2D(n.getEastNorth());
     250    }
     251
     252    // looses precision, may overflow (depends on p and current scale)
     253    //@Deprecated
     254    public Point getPoint(EastNorth p) {
     255        Point2D d = getPoint2D(p);
     256        return new Point((int) d.getX(), (int) d.getY());
    245257    }
     258
     259    // looses precision, may overflow (depends on p and current scale)
     260    //@Deprecated
     261    public Point getPoint(LatLon latlon) {
     262        Point2D d = getPoint2D(latlon);
     263        return new Point((int) d.getX(), (int) d.getY());
     264    }
     265
     266    // looses precision, may overflow (depends on p and current scale)
     267    //@Deprecated
    246268    public Point getPoint(Node n) {
    247         return getPoint(n.getEastNorth());
     269        Point2D d = getPoint2D(n);
     270        return new Point((int) d.getX(), (int) d.getY());
    248271    }
    249272
    250273    /**
     
    428451        return !zoomRedoBuffer.isEmpty();
    429452    }
    430453
    431     private BBox getSnapDistanceBBox(Point p) {
     454    private BBox getBBox(Point p, int snapDistance) {
    432455        return new BBox(getLatLon(p.x - snapDistance, p.y - snapDistance),
    433456                getLatLon(p.x + snapDistance, p.y + snapDistance));
    434457    }
    435458
    436     @Deprecated
    437     public final Node getNearestNode(Point p) {
    438         return getNearestNode(p, OsmPrimitive.isUsablePredicate);
     459    /**
     460     * The *result* does not depend on the current map selection state,
     461     * neither does the result *order*.
     462     * It solely depends on the distance to point p.
     463     *
     464     * @return a sorted map with the keys representing the distance of
     465     *      their associated nodes to point p.
     466     */
     467    private Map<Double, List<Node>> getNearestNodesImpl(Point p,
     468            Predicate<OsmPrimitive> predicate) {
     469        TreeMap<Double, List<Node>> nearestMap = new TreeMap<Double, List<Node>>();
     470        DataSet ds = getCurrentDataSet();
     471
     472        if (ds != null) {
     473            double dist, snapDistanceSq = Main.pref.getInteger("mappaint.node.snap-distance", 10);
     474            snapDistanceSq *= snapDistanceSq;
     475
     476            for (Node n : ds.searchNodes(getBBox(p, Main.pref.getInteger("mappaint.node.snap-distance", 10)))) {
     477                if (predicate.evaluate(n)
     478                        && (dist = getPoint2D(n).distanceSq(p)) < snapDistanceSq)
     479                {
     480                    List<Node> nlist;
     481                    if (nearestMap.containsKey(dist)) {
     482                        nlist = nearestMap.get(dist);
     483                    } else {
     484                        nlist = new LinkedList<Node>();
     485                        nearestMap.put(dist, nlist);
     486                    }
     487                    nlist.add(n);
     488                }
     489            }
     490        }
     491
     492        return nearestMap;
     493    }
     494
     495    /**
     496     * The *result* does not depend on the current map selection state,
     497     * neither does the result *order*.
     498     * It solely depends on the distance to point p.
     499     *
     500     * @return All nodes nearest to point p that are in a belt from
     501     *      dist(nearest) to dist(nearest)+4px around p and
     502     *      that are not in ignore.
     503     *
     504     * @param p the point for which to search the nearest segment.
     505     * @param ignore a collection of nodes which are not to be returned.
     506     * @param predicate the returned objects have to fulfill certain properties.
     507     */
     508    public final List<Node> getNearestNodes(Point p,
     509            Collection<Node> ignore, Predicate<OsmPrimitive> predicate) {
     510        List<Node> nearestList = Collections.emptyList();
     511
     512        if (ignore == null) {
     513            ignore = Collections.emptySet();
     514        }
     515
     516        Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
     517        if (!nlists.isEmpty()) {
     518            Double minDistSq = null;
     519            List<Node> nlist;
     520            for (Double distSq : nlists.keySet()) {
     521                nlist = nlists.get(distSq);
     522
     523                // filter nodes to be ignored before determining minDistSq..
     524                nlist.removeAll(ignore);
     525                if (minDistSq == null) {
     526                    if (!nlist.isEmpty()) {
     527                        minDistSq = distSq;
     528                        nearestList = new ArrayList<Node>();
     529                        nearestList.addAll(nlist);
     530                    }
     531                } else {
     532                    if (distSq-minDistSq < (4)*(4)) {
     533                        nearestList.addAll(nlist);
     534                    }
     535                }
     536            }
     537        }
     538
     539        return nearestList;
     540    }
     541
     542    /**
     543     * The *result* does not depend on the current map selection state,
     544     * neither does the result *order*.
     545     * It solely depends on the distance to point p.
     546     *
     547     * @return All nodes nearest to point p that are in a belt from
     548     *      dist(nearest) to dist(nearest)+4px around p.
     549     * @see #getNearestNodes(Point, Collection, Predicate)
     550     *
     551     * @param p the point for which to search the nearest segment.
     552     * @param predicate the returned objects have to fulfill certain properties.
     553     */
     554    public final List<Node> getNearestNodes(Point p, Predicate<OsmPrimitive> predicate) {
     555        return getNearestNodes(p, null, predicate);
    439556    }
    440557
    441558    /**
    442      * Return the nearest node to the screen point given.
    443      * If more then one node within snapDistance pixel is found,
    444      * the nearest node is returned.
     559     * The *result* depends on the current map selection state.
     560     *
     561     * If more than one node within node.snap-distance pixels is found,
     562     * the nearest node selected is returned.
     563     *
     564     * If no such node is found, the nearest new/id=0 node within
     565     * about the same distance as the true nearest node is returned.
     566     *
     567     * If no such node is found either, the true nearest
     568     * node to p is returned.
     569     *
     570     * Finally, if a node is not found at all, return null.
     571     *
     572     * @return A node within snap-distance to point p,
     573     *      that is chosen by the algorithm described.
     574     *
    445575     * @param p the screen point
    446576     * @param predicate this parameter imposes a condition on the returned object, e.g.
    447577     *        give the nearest node that is tagged.
    448578     */
    449579    public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
    450         DataSet ds = getCurrentDataSet();
    451         if (ds == null)
    452             return null;
     580        Node n = null;
    453581
    454         double minDistanceSq = snapDistanceSq;
    455         Node minPrimitive = null;
    456         for (Node n : ds.searchNodes(getSnapDistanceBBox(p))) {
    457             if (! predicate.evaluate(n))
    458                 continue;
    459             Point sp = getPoint(n);
    460             double dist = p.distanceSq(sp);
    461             if (dist < minDistanceSq) {
    462                 minDistanceSq = dist;
    463                 minPrimitive = n;
    464             }
    465             // when multiple nodes on one point, prefer new or selected nodes
    466             else if (dist == minDistanceSq && minPrimitive != null
    467                     && ((n.isNew() && ds.isSelected(n))
    468                             || (!ds.isSelected(minPrimitive) && (ds.isSelected(n) || n.isNew())))) {
    469                 minPrimitive = n;
     582        Map<Double, List<Node>> nlists = getNearestNodesImpl(p, predicate);
     583        if (!nlists.isEmpty()) {
     584            Node ntsel = null, ntnew = null;
     585            double minDistSq = nlists.keySet().iterator().next();
     586
     587            for (Double distSq : nlists.keySet()) {
     588                for (Node nd : nlists.get(distSq)) {
     589                    // find the nearest selected node
     590                    if (ntsel == null && nd.isSelected()) {
     591                        ntsel = nd;
     592                    }
     593                    // find the nearest newest node that is within about the same
     594                    // distance as the true nearest node
     595                    if (ntnew == null && nd.isNew() && (distSq-minDistSq < 1)) {
     596                        ntnew = nd;
     597                    }
     598                }
    470599            }
     600
     601            // take nearest selected, nearest new or true nearest node to p, in that order
     602            n = (ntsel != null) ? ntsel : ((ntnew != null) ? ntnew
     603                    : nlists.values().iterator().next().get(0));
    471604        }
    472         return minPrimitive;
     605
     606        return n;
     607    }
     608
     609    @Deprecated
     610    public final Node getNearestNode(Point p) {
     611        return getNearestNode(p, OsmPrimitive.isUsablePredicate);
    473612    }
    474613
    475614    /**
    476      * @return all way segments within 10px of p, sorted by their
    477      * perpendicular distance.
    478      *
    479      * @param p the point for which to search the nearest segment.
    480      * @param predicate the returned objects have to fulfill certain properties.
     615     * The *result* does not depend on the current map selection state,
     616     * neither does the result *order*.
     617     * It solely depends on the distance to point p.
     618     *
     619     * @return a sorted map with the keys representing the perpendicular
     620     *      distance of their associated way segments to point p.
    481621     */
    482     public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
    483         TreeMap<Double, List<WaySegment>> nearest = new TreeMap<Double, List<WaySegment>>();
     622    private Map<Double, List<WaySegment>> getNearestWaySegmentsImpl(Point p,
     623            Predicate<OsmPrimitive> predicate) {
     624        Map<Double, List<WaySegment>> nearestMap = new TreeMap<Double, List<WaySegment>>();
    484625        DataSet ds = getCurrentDataSet();
    485         if (ds == null)
    486             return null;
    487626
    488         for (Way w : ds.searchWays(getSnapDistanceBBox(p))) {
    489             if (!predicate.evaluate(w))
    490                 continue;
    491             Node lastN = null;
    492             int i = -2;
    493             for (Node n : w.getNodes()) {
    494                 i++;
    495                 if (n.isDeleted() || n.isIncomplete()) {//FIXME: This shouldn't happen, raise exception?
    496                     continue;
    497                 }
    498                 if (lastN == null) {
    499                     lastN = n;
     627        if (ds != null) {
     628            double snapDistanceSq = Main.pref.getInteger("mappaint.segment.snap-distance", 10);
     629            snapDistanceSq *= snapDistanceSq;
     630
     631            for (Way w : ds.searchWays(getBBox(p, Main.pref.getInteger("mappaint.segment.snap-distance", 10)))) {
     632                if (!predicate.evaluate(w)) {
    500633                    continue;
    501634                }
    502 
    503                 Point A = getPoint(lastN);
    504                 Point B = getPoint(n);
    505                 double c = A.distanceSq(B);
    506                 double a = p.distanceSq(B);
    507                 double b = p.distanceSq(A);
    508                 double perDist = a - (a - b + c) * (a - b + c) / 4 / c; // perpendicular distance squared
    509                 if (perDist < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
    510                     if (ds.isSelected(w)) {
    511                         perDist -= 0.00001;
     635                Node lastN = null;
     636                int i = -2;
     637                for (Node n : w.getNodes()) {
     638                    i++;
     639                    if (n.isDeleted() || n.isIncomplete()) { //FIXME: This shouldn't happen, raise exception?
     640                        continue;
    512641                    }
    513                     List<WaySegment> l;
    514                     if (nearest.containsKey(perDist)) {
    515                         l = nearest.get(perDist);
    516                     } else {
    517                         l = new LinkedList<WaySegment>();
    518                         nearest.put(perDist, l);
     642                    if (lastN == null) {
     643                        lastN = n;
     644                        continue;
     645                    }
     646
     647                    Point2D A = getPoint2D(lastN);
     648                    Point2D B = getPoint2D(n);
     649                    double c = A.distanceSq(B);
     650                    double a = p.distanceSq(B);
     651                    double b = p.distanceSq(A);
     652
     653                    /* perpendicular distance squared
     654                     * loose some precision to account for possible deviations in the calculation above
     655                     * e.g. if identical (A and B) come about reversed in another way, values may differ
     656                     * -- zero out least significant 32 dual digits of mantissa..
     657                     */
     658                    double perDistSq = Double.longBitsToDouble(
     659                            Double.doubleToLongBits( a - (a - b + c) * (a - b + c) / 4 / c )
     660                            >> 32 << 32); // resolution in numbers with large exponent not needed here..
     661
     662                    if (perDistSq < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
     663                        //System.err.println(Double.toHexString(perDistSq));
     664
     665                        List<WaySegment> wslist;
     666                        if (nearestMap.containsKey(perDistSq)) {
     667                            wslist = nearestMap.get(perDistSq);
     668                        } else {
     669                            wslist = new LinkedList<WaySegment>();
     670                            nearestMap.put(perDistSq, wslist);
     671                        }
     672                        wslist.add(new WaySegment(w, i));
    519673                    }
    520                     l.add(new WaySegment(w, i));
     674
     675                    lastN = n;
    521676                }
     677            }
     678        }
    522679
    523                 lastN = n;
     680        return nearestMap;
     681    }
     682
     683    /**
     684     * The result *order* depends on the current map selection state.
     685     * Segments within 10px of p are searched and sorted by their distance to @param p,
     686     * then, within groups of equally distant segments, prefer those that are selected.
     687     *
     688     * @return all segments within 10px of p that are not in ignore,
     689     *          sorted by their perpendicular distance.
     690     *
     691     * @param p the point for which to search the nearest segments.
     692     * @param ignore a collection of segments which are not to be returned.
     693     * @param predicate the returned objects have to fulfill certain properties.
     694     */
     695    public final List<WaySegment> getNearestWaySegments(Point p,
     696            Collection<WaySegment> ignore, Predicate<OsmPrimitive> predicate) {
     697        List<WaySegment> nearestList = new ArrayList<WaySegment>();
     698        List<WaySegment> unselected = new LinkedList<WaySegment>();
     699
     700        for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
     701            // put selected waysegs within each distance group first
     702            // makes the order of nearestList dependent on current selection state
     703            for (WaySegment ws : wss) {
     704                (ws.way.isSelected() ? nearestList : unselected).add(ws);
    524705            }
     706            nearestList.addAll(unselected);
     707            unselected.clear();
    525708        }
    526         ArrayList<WaySegment> nearestList = new ArrayList<WaySegment>();
    527         for (List<WaySegment> wss : nearest.values()) {
    528             nearestList.addAll(wss);
     709        if (ignore != null) {
     710            nearestList.removeAll(ignore);
    529711        }
     712
    530713        return nearestList;
    531714    }
    532715
    533716    /**
    534      * @return the nearest way segment to the screen point given that is not
    535      * in ignore.
     717     * The result *order* depends on the current map selection state.
     718     *
     719     * @return all segments within 10px of p, sorted by their perpendicular distance.
     720     * @see #getNearestWaySegments(Point, Collection, Predicate)
     721     *
     722     * @param p the point for which to search the nearest segments.
     723     * @param predicate the returned objects have to fulfill certain properties.
     724     */
     725    public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
     726        return getNearestWaySegments(p, null, predicate);
     727    }
     728
     729    /**
     730     * The *result* depends on the current map selection state.
     731     *
     732     * @return The nearest way segment to point p,
     733     *      prefer a nearest, selected way segment, if found.
     734     * @see #getNearestWaySegments(Point, Collection, Predicate)
    536735     *
    537736     * @param p the point for which to search the nearest segment.
    538      * @param ignore a collection of segments which are not to be returned.
     737     * @param predicate the returned object has to fulfill certain properties.
     738     */
     739    public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
     740        WaySegment wayseg = null, ntsel = null;
     741
     742        for (List<WaySegment> wslist : getNearestWaySegmentsImpl(p, predicate).values()) {
     743            if (wayseg != null && ntsel != null) {
     744                break;
     745            }
     746            for (WaySegment ws : wslist) {
     747                if (wayseg == null) {
     748                    wayseg = ws;
     749                }
     750                if (ntsel == null && ws.way.isSelected()) {
     751                    ntsel = ws;
     752                }
     753            }
     754        }
     755
     756        return (ntsel != null) ? ntsel : wayseg;
     757    }
     758
     759    /**
     760     * The *result* does not depend on the current map selection state,
     761     * neither does the result *order*.
     762     * It solely depends on the perpendicular distance to point p.
     763     *
     764     * @return all nearest ways to the screen point given that are not in ignore.
     765     * @see #getNearestWaySegments(Point, Collection, Predicate)
     766     *
     767     * @param p the point for which to search the nearest ways.
     768     * @param ignore a collection of ways which are not to be returned.
    539769     * @param predicate the returned object has to fulfill certain properties.
    540      * May be null.
    541770     */
    542     public final WaySegment getNearestWaySegment
    543                                     (Point p, Collection<WaySegment> ignore, Predicate<OsmPrimitive> predicate) {
    544         List<WaySegment> nearest = getNearestWaySegments(p, predicate);
    545         if(nearest == null)
    546             return null;
     771    public final List<Way> getNearestWays(Point p,
     772            Collection<Way> ignore, Predicate<OsmPrimitive> predicate) {
     773        List<Way> nearestList = new ArrayList<Way>();
     774        Set<Way> wset = new HashSet<Way>();
     775
     776        for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
     777            for (WaySegment ws : wss) {
     778                if (wset.add(ws.way)) {
     779                    nearestList.add(ws.way);
     780                }
     781            }
     782        }
    547783        if (ignore != null) {
    548             nearest.removeAll(ignore);
     784            nearestList.removeAll(ignore);
    549785        }
    550         return nearest.isEmpty() ? null : nearest.get(0);
     786
     787        return nearestList;
    551788    }
    552789
    553790    /**
    554      * @return the nearest way segment to the screen point given.
     791     * The *result* does not depend on the current map selection state,
     792     * neither does the result *order*.
     793     * It solely depends on the perpendicular distance to point p.
     794     *
     795     * @return all nearest ways to the screen point given.
     796     * @see #getNearestWays(Point, Collection, Predicate)
     797     *
     798     * @param p the point for which to search the nearest ways.
     799     * @param predicate the returned object has to fulfill certain properties.
    555800     */
    556     public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
    557         return getNearestWaySegment(p, null, predicate);
     801    public final List<Way> getNearestWays(Point p, Predicate<OsmPrimitive> predicate) {
     802        return getNearestWays(p, null, predicate);
     803    }
     804
     805    /**
     806     * The *result* depends on the current map selection state.
     807     *
     808     * @return The nearest way to point p,
     809     *      prefer a selected way if there are multiple nearest.
     810     * @see #getNearestWaySegment(Point, Collection, Predicate)
     811     *
     812     * @param p the point for which to search the nearest segment.
     813     * @param predicate the returned object has to fulfill certain properties.
     814     */
     815    public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
     816        WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
     817        return (nearestWaySeg == null) ? null : nearestWaySeg.way;
    558818    }
    559819
    560820    @Deprecated
     
    563823    }
    564824
    565825    /**
    566      * @return the nearest way to the screen point given.
     826     * The *result* does not depend on the current map selection state,
     827     * neither does the result *order*.
     828     * It solely depends on the distance to point p.
     829     *
     830     * First, nodes will be searched. If there are nodes within BBox found,
     831     * return a collection of those nodes only.
     832     *
     833     * If no nodes are found, search for nearest ways. If there are ways
     834     * within BBox found, return a collection of those ways only.
     835     *
     836     * If nothing is found, return an empty collection.
     837     *
     838     * @return Primitives nearest to the given screen point that are not in ignore.
     839     * @see #getNearestNodes(Point, Collection, Predicate)
     840     * @see #getNearestWays(Point, Collection, Predicate)
     841     *
     842     * @param p The point on screen.
     843     * @param ignore a collection of ways which are not to be returned.
     844     * @param predicate the returned object has to fulfill certain properties.
    567845     */
    568     public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
    569         WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
    570         return nearestWaySeg == null ? null : nearestWaySeg.way;
     846    public final List<OsmPrimitive> getNearestNodesOrWays(Point p,
     847            Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
     848        List<OsmPrimitive> nearestList = Collections.emptyList();
     849        OsmPrimitive osm = getNearestNodeOrWay(p, predicate, false);
     850
     851        if (osm != null) {
     852            if (osm instanceof Node) {
     853                nearestList = new ArrayList<OsmPrimitive>(getNearestNodes(p, predicate));
     854            } else if (osm instanceof Way) {
     855                nearestList = new ArrayList<OsmPrimitive>(getNearestWays(p, predicate));
     856            }
     857            if (ignore != null) {
     858                nearestList.removeAll(ignore);
     859            }
     860        }
     861
     862        return nearestList;
    571863    }
    572864
    573865    /**
    574      * Return the object, that is nearest to the given screen point.
    575      *
    576      * First, a node will be searched. If a node within 10 pixel is found, the
    577      * nearest node is returned.
    578      *
    579      * If no node is found, search for near ways.
    580      *
    581      * If nothing is found, return <code>null</code>.
    582      *
     866     * The *result* does not depend on the current map selection state,
     867     * neither does the result *order*.
     868     * It solely depends on the distance to point p.
     869     *
     870     * @return Primitives nearest to the given screen point.
     871     * @see #getNearests(Point, Collection, Predicate)
     872     *
    583873     * @param p The point on screen.
    584874     * @param predicate the returned object has to fulfill certain properties.
    585      * @return  The primitive that is nearest to the point p.
    586875     */
    587     public OsmPrimitive getNearest(Point p, Predicate<OsmPrimitive> predicate) {
    588         OsmPrimitive osm = getNearestNode(p, predicate);
    589         if (osm == null)
    590         {
    591             osm = getNearestWay(p, predicate);
    592         }
    593         return osm;
     876    public final List<OsmPrimitive> getNearestNodesOrWays(Point p, Predicate<OsmPrimitive> predicate) {
     877        return getNearestNodesOrWays(p, null, predicate);
    594878    }
    595879
    596880    /**
    597      * Returns a singleton of the nearest object, or else an empty collection.
     881     * This is used as a helper routine to {@link #getNearestNodeOrWay(Point, Predicate, boolean)}
     882     *
     883     * @return true, if the node fulfills certain properties wrt p and use_sel
     884     *
     885     * @param osm node to check
     886     * @param p point clicked
     887     * @param use_sel whether to prefer a selected node
    598888     */
    599     public Collection<OsmPrimitive> getNearestCollection(Point p, Predicate<OsmPrimitive> predicate) {
    600         OsmPrimitive osm = getNearest(p, predicate);
    601         if (osm == null)
    602             return Collections.emptySet();
    603         return Collections.singleton(osm);
     889    private boolean isPrecedenceNode(Node osm, Point p, boolean use_selected) {
     890        boolean ret = false;
     891
     892        if (osm != null) {
     893            ret |= !(p.distanceSq(getPoint2D(osm)) > (4)*(4));
     894            ret |= osm.isTagged();
     895            if (use_selected) {
     896                ret |= osm.isSelected();
     897            }
     898        }
     899
     900        return ret;
    604901    }
    605902
    606903    /**
    607      * @return A list of all objects that are nearest to
    608      * the mouse.
     904     * The *result* depends on the current map selection state IF use_selected is true.
     905     *
     906     * IF use_selected is true, use {@link #getNearestNode(Point, Predicate)} to find
     907     * the nearest, selected node.  If not found, try {@link #getNearestWaySegment(Point, Predicate)}
     908     * to find the nearest selected way.
     909     *
     910     * IF use_selected is false, or if no selected primitive was found, do the following.
     911     *
     912     * If the nearest node found is within 4px of p, simply take it.
     913     * Else, find the nearest way segment. Then, if p is closer to its
     914     * middle than to the node, take the way segment, else take the node.
     915     *
     916     * Finally, if no nearest primitive is found at all, return null.
     917     *
     918     * @return A primitive within snap-distance to point p,
     919     *      that is chosen by the algorithm described.
     920     * @see getNearestNode(Point, Predicate)
     921     * @see getNearestNodesImpl(Point, Predicate)
     922     * @see getNearestWay(Point, Predicate)
    609923     *
    610      * @return A collection of all items or <code>null</code>
    611      *      if no item under or near the point. The returned
    612      *      list is never empty.
     924     * @param p The point on screen.
     925     * @param predicate the returned object has to fulfill certain properties.
     926     * @param use_selected whether to prefer primitives that are currently selected.
    613927     */
    614     public Collection<OsmPrimitive> getAllNearest(Point p, Predicate<OsmPrimitive> predicate) {
    615         Collection<OsmPrimitive> nearest = new HashSet<OsmPrimitive>();
    616         DataSet ds = getCurrentDataSet();
    617         if (ds == null)
    618             return null;
    619         for (Way w : ds.searchWays(getSnapDistanceBBox(p))) {
    620             if (!predicate.evaluate(w))
    621                 continue;
    622             Node lastN = null;
    623             for (Node n : w.getNodes()) {
    624                 if (!predicate.evaluate(n))
    625                     continue;
    626                 if (lastN == null) {
    627                     lastN = n;
    628                     continue;
     928    public final OsmPrimitive getNearestNodeOrWay(Point p, Predicate<OsmPrimitive> predicate, boolean use_selected) {
     929        OsmPrimitive osm = null;
     930        WaySegment ws = null;
     931
     932        // find a nearest, selected primitive
     933        if (use_selected) {
     934            osm = getNearestNode(p, predicate);
     935            use_selected = isPrecedenceNode((Node)osm, p, use_selected);
     936
     937            if (!use_selected) {
     938                ws = getNearestWaySegment(p, predicate);
     939                if (ws != null) {
     940                    if (ws.way.isSelected() || osm == null) {
     941                        // either no nearest nodes found or none were selected
     942                        use_selected = true;
     943                        osm = ws.way;
     944                    } // else { unselected node and wayseg found, do the stuff below }
     945                } else {
     946                    // no nearest wayseg found, osm is null or the nearest node
     947                    use_selected = true;
    629948                }
    630                 Point A = getPoint(lastN);
    631                 Point B = getPoint(n);
    632                 double c = A.distanceSq(B);
    633                 double a = p.distanceSq(B);
    634                 double b = p.distanceSq(A);
    635                 double perDist = a - (a - b + c) * (a - b + c) / 4 / c; // perpendicular distance squared
    636                 if (perDist < snapDistanceSq && a < c + snapDistanceSq && b < c + snapDistanceSq) {
    637                     nearest.add(w);
     949            }
     950        }
     951
     952        // no nearest, selected primitive was found or caller does not care about current selection
     953        if (!use_selected) {
     954            if (osm == null) {
     955                // get the true nearest node (if unselected found before, reuse it)
     956                for (List<Node> nlist : getNearestNodesImpl(p, predicate).values()) {
     957                    osm = nlist.get(0);
    638958                    break;
    639959                }
    640                 lastN = n;
    641960            }
    642         }
    643         for (Node n : ds.searchNodes(getSnapDistanceBBox(p))) {
    644             if (n.isUsable()
    645                     && getPoint(n).distanceSq(p) < snapDistanceSq) {
    646                 nearest.add(n);
     961
     962            // there is no nearest node OR it does not fulfill criteria to simply be chosen
     963            if (osm == null || !isPrecedenceNode((Node)osm, p, use_selected)) {
     964                if (ws == null) {
     965                    // get the true nearest wayseg (if unselected found before, reuse it)
     966                    for (WaySegment wseg : getNearestWaySegments(p, predicate)) {
     967                        ws = wseg;
     968                        break;
     969                    }
     970                }
     971                if (ws != null) {
     972                    if (osm == null) {
     973                        // a nearest node was not found
     974                        osm = ws.way;
     975                    } else {
     976                        int maxWaySegLenSq = 3*Main.pref.getInteger("mappaint.node.snap-distance", 10);
     977                        maxWaySegLenSq *= maxWaySegLenSq;
     978
     979                        Point2D wp1 = getPoint2D(ws.way.getNode(ws.lowerIndex));
     980                        Point2D wp2 = getPoint2D(ws.way.getNode(ws.lowerIndex+1));
     981
     982                        // is wayseg shorter than maxWaySegLenSq and
     983                        // is p closer to the middle of wayseg than to the nearest node?
     984                        if (wp1.distanceSq(wp2) < maxWaySegLenSq &&
     985                                p.distanceSq(project(0.5, wp1, wp2)) < p.distanceSq(getPoint2D((Node)osm))) {
     986                            osm = ws.way;
     987                        }
     988                    }
     989                }
    647990            }
    648991        }
    649         return nearest.isEmpty() ? null : nearest;
     992
     993        return osm;
    650994    }
    651995
    652996    /**
    653      * @return A list of all nodes that are nearest to
    654      * the mouse.
    655      *
    656      * @return A collection of all nodes or <code>null</code>
    657      *      if no node under or near the point. The returned
    658      *      list is never empty.
     997     * Convenience method to {@link #getNearestNodeOrWay(Point, Predicate, boolean)}.
     998     *
     999     * @return The nearest primitive to point p.
    6591000     */
    660     public Collection<Node> getNearestNodes(Point p, Predicate<OsmPrimitive> predicate) {
    661         Collection<Node> nearest = new HashSet<Node>();
    662         DataSet ds = getCurrentDataSet();
    663         if (ds == null)
    664             return null;
     1001    public final OsmPrimitive getNearestNodeOrWay(Point p, Predicate<OsmPrimitive> predicate) {
     1002        return getNearestNodeOrWay(p, predicate, false);
     1003    }
    6651004
    666         for (Node n : ds.searchNodes(getSnapDistanceBBox(p))) {
    667             if (!predicate.evaluate(n))
    668                 continue;
    669             if (getPoint(n).distanceSq(p) < snapDistanceSq) {
    670                 nearest.add(n);
    671             }
     1005    @Deprecated
     1006    public final OsmPrimitive getNearest(Point p, Predicate<OsmPrimitive> predicate) {
     1007        return getNearestNodeOrWay(p, predicate, false);
     1008    }
     1009
     1010    @Deprecated
     1011    public final Collection<OsmPrimitive> getNearestCollection(Point p, Predicate<OsmPrimitive> predicate) {
     1012        return asColl(getNearest(p, predicate));
     1013    }
     1014
     1015    /**
     1016     * @return o as collection of o's type.
     1017     */
     1018    public final static <T> Collection<T> asColl(T o) {
     1019        if (o == null)
     1020            return Collections.emptySet();
     1021        return Collections.singleton(o);
     1022    }
     1023
     1024    public final static double perDist(Point2D pt, Point2D a, Point2D b) {
     1025        if (pt != null && a != null && b != null) {
     1026            double pd = (
     1027                    (a.getX()-pt.getX())*(b.getX()-a.getX()) -
     1028                    (a.getY()-pt.getY())*(b.getY()-a.getY()) );
     1029            return Math.abs(pd) / a.distance(b);
    6721030        }
    673         return nearest.isEmpty() ? null : nearest;
     1031        return 0d;
    6741032    }
    6751033
    6761034    /**
    677      * @return the nearest nodes to the screen point given that is not
    678      * in ignore.
    679      *
    680      * @param p the point for which to search the nearest segment.
    681      * @param ignore a collection of nodes which are not to be returned.
    682      * @param predicate the returned objects have to fulfill certain properties.
    683      * May be null.
     1035     *
     1036     * @param pt point to project onto (ab)
     1037     * @param a root of vector
     1038     * @param b vector
     1039     * @return point of intersection of line given by (ab)
     1040     *      with its orthogonal line running through pt
     1041     */
     1042    public final static Point2D project(Point2D pt, Point2D a, Point2D b) {
     1043        if (pt != null && a != null && b != null) {
     1044            double r = ((
     1045                    (pt.getX()-a.getX())*(b.getX()-a.getX()) +
     1046                    (pt.getY()-a.getY())*(b.getY()-a.getY()) )
     1047                    / a.distanceSq(b));
     1048            return project(r, a, b);
     1049        }
     1050        return null;
     1051    }
     1052
     1053    /**
     1054     * if r = 0 returns a, if r=1 returns b,
     1055     * if r = 0.5 returns center between a and b, etc..
     1056     *
     1057     * @param r scale value
     1058     * @param a root of vector
     1059     * @param b vector
     1060     * @return new point at a + r*(ab)
     1061     */
     1062    public final static Point2D project(double r, Point2D a, Point2D b) {
     1063        Point2D ret = null;
     1064
     1065        if (a != null && b != null) {
     1066            ret = new Point2D.Double(a.getX() + r*(b.getX()-a.getX()),
     1067                    a.getY() + r*(b.getY()-a.getY()));
     1068        }
     1069        return ret;
     1070    }
     1071
     1072    /**
     1073     * The *result* does not depend on the current map selection state,
     1074     * neither does the result *order*.
     1075     * It solely depends on the distance to point p.
     1076     *
     1077     * @return a list of all objects that are nearest to point p and
     1078     *          not in ignore or an empty list if nothing was found.
     1079     *
     1080     * @param p The point on screen.
     1081     * @param ignore a collection of ways which are not to be returned.
     1082     * @param predicate the returned object has to fulfill certain properties.
    6841083     */
    685     public final Collection<Node> getNearestNodes(Point p, Collection<Node> ignore, Predicate<OsmPrimitive> predicate) {
    686         Collection<Node> nearest = getNearestNodes(p, predicate);
    687         if (nearest == null) return null;
     1084    public final List<OsmPrimitive> getAllNearest(Point p,
     1085            Collection<OsmPrimitive> ignore, Predicate<OsmPrimitive> predicate) {
     1086        List<OsmPrimitive> nearestList = new ArrayList<OsmPrimitive>();
     1087        Set<Way> wset = new HashSet<Way>();
     1088
     1089        for (List<WaySegment> wss : getNearestWaySegmentsImpl(p, predicate).values()) {
     1090            for (WaySegment ws : wss) {
     1091                if (wset.add(ws.way)) {
     1092                    nearestList.add(ws.way);
     1093                }
     1094            }
     1095        }
     1096        for (List<Node> nlist : getNearestNodesImpl(p, predicate).values()) {
     1097            nearestList.addAll(nlist);
     1098        }
    6881099        if (ignore != null) {
    689             nearest.removeAll(ignore);
     1100            nearestList.removeAll(ignore);
    6901101        }
    691         return nearest.isEmpty() ? null : nearest;
     1102
     1103        return nearestList;
     1104    }
     1105
     1106    /**
     1107     * The *result* does not depend on the current map selection state,
     1108     * neither does the result *order*.
     1109     * It solely depends on the distance to point p.
     1110     *
     1111     * @return a list of all objects that are nearest to point p
     1112     *          or an empty list if nothing was found.
     1113     * @see #getAllNearest(Point, Collection, Predicate)
     1114     *
     1115     * @param p The point on screen.
     1116     * @param predicate the returned object has to fulfill certain properties.
     1117     */
     1118    public final List<OsmPrimitive> getAllNearest(Point p, Predicate<OsmPrimitive> predicate) {
     1119        return getAllNearest(p, null, predicate);
    6921120    }
    6931121
    6941122    /**
  • src/org/openstreetmap/josm/gui/MapView.java

     
    3535import org.openstreetmap.josm.actions.AutoScaleAction;
    3636import org.openstreetmap.josm.actions.mapmode.MapMode;
    3737import org.openstreetmap.josm.data.Bounds;
    38 import org.openstreetmap.josm.data.SelectionChangedListener;
    3938import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
    4039import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
     40import org.openstreetmap.josm.data.SelectionChangedListener;
    4141import org.openstreetmap.josm.data.coor.LatLon;
    4242import org.openstreetmap.josm.data.osm.DataSet;
    4343import org.openstreetmap.josm.data.osm.DataSource;
  • src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java

     
    1818import java.awt.event.MouseAdapter;
    1919import java.awt.event.MouseEvent;
    2020import java.net.HttpURLConnection;
     21import java.net.ProxySelector;
    2122import java.net.URI;
    2223import java.net.URLEncoder;
    2324import java.util.ArrayList;
     
    10601061            try {
    10611062                String base = new String(Main.pref.get("url.openstreetmap-wiki", "http://wiki.openstreetmap.org/wiki/"));
    10621063                String l = LanguageInfo.getWikiLanguagePrefix();
    1063                 List<URI> uris = new ArrayList<URI>();
     1064                final List<URI> uris = new ArrayList<URI>();
    10641065                int row;
    10651066                if (propertyTable.getSelectedRowCount() == 1) {
    10661067                    row = propertyTable.getSelectedRow();
     
    10961097                    uris.add(new URI(String.format("%sMap_Features", base)));
    10971098                }
    10981099
    1099                 // find a page that actually exists in the wiki
    1100                 HttpURLConnection conn;
    1101                 for(URI u : uris) {
    1102                     conn = (HttpURLConnection) u.toURL().openConnection();
     1100                Main.worker.execute(new Runnable(){
     1101                    public void run() {
     1102                        try {
     1103                            // find a page that actually exists in the wiki
     1104                            HttpURLConnection conn;
     1105                            for(URI u : uris) {
     1106                                conn = (HttpURLConnection) u.toURL().openConnection(ProxySelector.getDefault().select(u).get(0));
    11031107
    1104                     if (conn.getResponseCode() != 200) {
    1105                         System.out.println("INFO: " + u + " does not exist");
    1106                         conn.disconnect();
    1107                     } else {
    1108                         int osize = conn.getContentLength();
    1109                         conn.disconnect();
     1108                                if (conn.getResponseCode() != 200) {
     1109                                    System.out.println("INFO: " + u + " does not exist");
     1110                                    conn.disconnect();
     1111                                } else {
     1112                                    int osize = conn.getContentLength();
     1113                                    conn.disconnect();
    11101114
    1111                         conn = (HttpURLConnection) new URI(u.toString()
    1112                                 .replace("=", "%3D") /* do not URLencode whole string! */
    1113                                 .replaceFirst("/wiki/", "/w/index.php?redirect=no&title=")
    1114                         ).toURL().openConnection();
     1115                                    conn = (HttpURLConnection) new URI(u.toString()
     1116                                            .replace("=", "%3D") /* do not URLencode whole string! */
     1117                                            .replaceFirst("/wiki/", "/w/index.php?redirect=no&title=")
     1118                                    ).toURL().openConnection();
    11151119
    1116                         /* redirect pages have different content length, but retrieving a "nonredirect"
    1117                          *  page using index.php and the direct-link method gives slightly different
    1118                          *  content lengths, so we have to be fuzzy.. (this is UGLY, recode if u know better)
    1119                          */
    1120                         if (Math.abs(conn.getContentLength()-osize) > 200) {
    1121                             System.out.println("INFO: " + u + " is a mediawiki redirect");
    1122                             conn.disconnect();
    1123                         } else {
    1124                             System.out.println("INFO: browsing to " + u);
    1125                             conn.disconnect();
     1120                                    /* redirect pages have different content length, but retrieving a "nonredirect"
     1121                                     *  page using index.php and the direct-link method gives slightly different
     1122                                     *  content lengths, so we have to be fuzzy.. (this is UGLY, recode if u know better)
     1123                                     */
     1124                                    if (Math.abs(conn.getContentLength()-osize) > 200) {
     1125                                        System.out.println("INFO: " + u + " is a mediawiki redirect");
     1126                                        conn.disconnect();
     1127                                    } else {
     1128                                        System.out.println("INFO: browsing to " + u);
     1129                                        conn.disconnect();
    11261130
    1127                             OpenBrowser.displayUrl(u.toString());
    1128                             break;
     1131                                        OpenBrowser.displayUrl(u.toString());
     1132                                        break;
     1133                                    }
     1134                                }
     1135                            }
     1136                        } catch (Exception e) {
     1137                            e.printStackTrace();
    11291138                        }
    11301139                    }
    1131                 }
     1140                });
    11321141            } catch (Exception e1) {
    11331142                e1.printStackTrace();
    11341143            }
  • src/org/openstreetmap/josm/gui/MapStatus.java

     
    288288         * @param ms
    289289         */
    290290        private final void statusBarElementUpdate(MouseState ms) {
    291             final OsmPrimitive osmNearest = mv.getNearest(ms.mousePos, OsmPrimitive.isUsablePredicate);
     291            final OsmPrimitive osmNearest = mv.getNearestNodeOrWay(ms.mousePos, OsmPrimitive.isUsablePredicate);
    292292            if (osmNearest != null) {
    293293                nameText.setText(osmNearest.getDisplayName(DefaultNameFormatter.getInstance()));
    294294            } else {