Changeset 3594 in josm for trunk/src/org


Ignore:
Timestamp:
2010-10-09T00:37:35+02:00 (14 years ago)
Author:
bastiK
Message:

applied #5500 (patch by cmuelle8) - improve SelectAction and NavigatableComponent

Location:
trunk/src/org/openstreetmap/josm
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java

    r3593 r3594  
    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;
     
    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;
     
    6563    enum Mode { move, rotate, select }
    6664    private Mode mode = null;
     65    private SelectionManager selectionManager;
     66
     67    private boolean cancelDrawMode = false;
     68    private boolean didMouseDrag = false;
     69
     70    /**
     71     * The component this SelectAction is associated with.
     72     */
     73    private final MapView mv;
     74
     75    /**
     76     * The old cursor before the user pressed the mouse button.
     77     */
     78    private Cursor oldCursor;
     79
     80    /**
     81     * The position of the mouse before the user moves a node.
     82     */
     83    private Point mousePos;
     84
     85    /**
     86     * The time of the user mouse down event.
     87     */
    6788    private long mouseDownTime = 0;
    68     private boolean didMove = false;
    69     private boolean cancelDrawMode = false;
    70     private Node virtualNode = null;
    71     private Collection<WaySegment> virtualWays = new ArrayList<WaySegment>();
    72 
    73     /**
    74      * The old cursor before the user pressed the mouse button.
    75      */
    76     private Cursor oldCursor;
    77     /**
    78      * The position of the mouse before the user moves a node.
    79      */
    80     private Point mousePos;
    81     private SelectionManager selectionManager;
    8289
    8390    /**
     
    93100    private int initialMoveThreshold;
    94101    private boolean initialMoveThresholdExceeded = false;
     102
    95103    /**
    96104     * Create a new SelectAction
     
    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);
     
    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    }
     
    125134    private void restoreCursor() {
    126135        if (oldCursor != null) {
    127             Main.map.mapView.setCursor(oldCursor);
     136            mv.setCursor(oldCursor);
    128137            oldCursor = null;
    129138        }
     
    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    }
     
    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
     
    152161     */
    153162    @Override public void mouseDragged(MouseEvent e) {
    154         if(!Main.map.mapView.isActiveLayerVisible())
     163        if(!mv.isActiveLayerVisible())
    155164            return;
    156165
     
    167176        if (mode == Mode.move) {
    168177            setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
    169         }
    170 
    171         if (mousePos == null) {
    172             mousePos = e.getPoint();
    173             return;
    174178        }
    175179
     
    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();
     
    255259        }
    256260
    257         Main.map.mapView.repaint();
     261        mv.repaint();
    258262        mousePos = e.getPoint();
    259263
    260         didMove = true;
     264        didMouseDrag = true;
    261265    }
    262266
     
    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();
    278 
    279         // take nearest node
    280         OsmPrimitive osm = c.getNearestNode(p, OsmPrimitive.isSelectablePredicate);
    281 
    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;
     275    private Node virtualNode = null;
     276    private Collection<WaySegment> virtualWays = new LinkedList<WaySegment>();
     277
     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;
     291
     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                 }
    306 
    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))
     297            for(WaySegment ws : mv.getNearestWaySegments(p, OsmPrimitive.isSelectablePredicate)) {
     298                w = ws.way;
     299
     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()));
    331314                        }
    332                     }
    333                 }
    334             }
    335 
    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;
    340             }
    341 
    342             if (osm == null && !wss.isEmpty()) {
    343                 // take nearest way
    344                 osm = wss.iterator().next().way;
    345             }
    346         }
    347 
    348         if (osm == null)
    349             return Collections.emptySet();
    350         return Collections.singleton(osm);
     315                        if (vnp.equals(wnp)) {
     316                            (w.isSelected() ? selVirtualWays : virtualWays).add(ws);
     317                        }
     318                    }
     319                }
     320            }
     321
     322            if (!selVirtualWays.isEmpty()) {
     323                virtualWays = selVirtualWays;
     324            }
     325        }
     326
     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                }
     392            }
     393        }
     394
     395        return MapView.asColl(osm);
    351396    }
    352397
     
    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();
    368 
    369         cancelDrawMode = false;
    370         if (! (Boolean)this.getValue("active")) return;
    371         if (e.getButton() != MouseEvent.BUTTON1)
    372             return;
     417        mv.requestFocus();
     418
    373419        boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    374420        boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     
    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 
     424        cancelDrawMode = (shift || ctrl);
     425        didMouseDrag = false;
     426        initialMoveThresholdExceeded = false;
    382427        mouseDownTime = System.currentTimeMillis();
    383         didMove = false;
    384         initialMoveThresholdExceeded = false;
    385 
    386         Collection<OsmPrimitive> osmColl = getNearestCollectionVirtual(e.getPoint());
    387 
    388         if (ctrl && shift) {
     428        mousePos = e.getPoint();
     429
     430        Collection<OsmPrimitive> c = MapView.asColl(
     431                mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, false));
     432
     433        if (shift && ctrl) {
     434            mode = Mode.rotate;
     435
    389436            if (getCurrentDataSet().getSelected().isEmpty()) {
    390                 selectPrims(osmColl, true, false, false, false);
    391             }
    392             mode = Mode.rotate;
     437                getCurrentDataSet().setSelected(c);
     438            }
     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);
     455
     456            oldCursor = mv.getCursor();
     457            selectionManager.register(mv);
    407458            selectionManager.mousePressed(e);
    408459        }
    409460
    410         if(mode != Mode.move || shift || ctrl) {
    411             virtualNode = null;
    412             virtualWays.clear();
    413         }
    414 
    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();
    424     }
    425 
    426     /**
    427      * Restore the old mouse cursor.
    428      */
    429     @Override public void mouseReleased(MouseEvent e) {
    430         if(!Main.map.mapView.isActiveLayerVisible())
     462    }
     463
     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
     
    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);
    449 
    450             virtualWays.clear();
    451             virtualNode = null;
    452 
    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);
    462                     }
    463                 }
    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;
     483            if (!didMouseDrag) {
     484                // only built in move mode
     485                virtualWays.clear();
     486                virtualNode = null;
     487
     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);
     492
     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;
     504                    }
    477505                }
    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());
    490                     }
    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                         }
     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();
     511                    }
     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                         }
    525                     }
     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();
     531                    }
     532                } else {
     533                    mergePrims(getCurrentDataSet().getSelectedNodes(), e);
    526534                }
    527535                getCurrentDataSet().fireSelectionChanged();
     
    535543    }
    536544
    537     public void selectionEnded(Rectangle r, boolean alt, boolean shift, boolean ctrl) {
    538         selectPrims(selectionManager.getObjectsInRectangle(r, alt), shift, ctrl, true, true);
    539     }
    540 
    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) {
     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);
     548    }
     549
     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;
     560
     561        debug("cyclePrims(): entry.....");
     562        for (OsmPrimitive osm : prims) {
     563            debug("cyclePrims(): prims id=" + osm.getId());
     564        }
     565
     566        if (prims.size() > 1) {
     567            boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
     568            boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     569
     570            DataSet ds = getCurrentDataSet();
     571            OsmPrimitive first = prims.iterator().next(), foundInDS = null;
     572            nxt = first;
     573
     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;
     586                        }
     587                        debug("selectPrims(): taking " + nxt.getId());
     588                        break; // take next primitive in prims list
     589                    }
     590                }
     591            }
     592
     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");
     605                    }
     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());
     611                }
     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;
     625
     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                }
     636            }
     637        }
     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;
    546643        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);
    552644
    553645        // not allowed together: do not change dataset selection, return early
     
    555647            return;
    556648
    557         // toggle through possible objects on mouse release
    558         if (released && !area) {
    559             if (selectionList.size() > 1) {
    560                 Collection<OsmPrimitive> coll = ds.getSelected();
    561 
    562                 OsmPrimitive first, foundInDS, node, nxt;
    563                 first = nxt = selectionList.iterator().next();
    564                 foundInDS = node = null;
    565 
    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;
    581                         }
    582                     }
    583                 }
    584 
    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;
    599                     }
    600                 }
    601 
    602                 selectionList = new ArrayList<OsmPrimitive>(1); // do not modify the passed object..
    603                 selectionList.add(nxt);
    604             }
     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);
    605654        }
    606655
     
    610659            // out of the selection.
    611660            if (area) {
    612                 ds.clearSelection(selectionList);
     661                ds.clearSelection(prims);
    613662            } else {
    614                 ds.toggleSelected(selectionList);
    615             }
     663                ds.toggleSelected(prims);
     664            }
     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    }
     
    640686        return l instanceof OsmDataLayer;
    641687    }
     688
     689    private static void debug(String s) {
     690        //System.err.println("SelectAction:" + s);
     691    }
    642692}
  • trunk/src/org/openstreetmap/josm/actions/mapmode/ZoomAction.java

    r3443 r3594  
    66import java.awt.Rectangle;
    77import java.awt.event.KeyEvent;
     8import java.awt.event.MouseEvent;
    89
    910import org.openstreetmap.josm.Main;
     
    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;
  • trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/SimplePaintVisitor.java

    r3565 r3594  
    1313import java.awt.Stroke;
    1414import java.awt.geom.GeneralPath;
     15import java.awt.geom.Point2D;
    1516import java.util.Collection;
    1617import java.util.Iterator;
     
    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
     
    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);
     
    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    }
  • trunk/src/org/openstreetmap/josm/gui/MapStatus.java

    r3438 r3594  
    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()));
  • trunk/src/org/openstreetmap/josm/gui/MapView.java

    r3478 r3594  
    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;
  • trunk/src/org/openstreetmap/josm/gui/NavigatableComponent.java

    r3490 r3594  
    66import java.awt.Point;
    77import java.awt.Rectangle;
     8import java.awt.geom.Point2D;
    89import java.util.ArrayList;
    910import java.util.Collection;
     
    1617import java.util.Locale;
    1718import java.util.Map;
     19import java.util.Set;
    1820import java.util.Stack;
    1921import java.util.TreeMap;
     
    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,
     
    194192    }
    195193
     194    public LatLon getLatLon(double x, double y) {
     195        return getLatLon((int)x, (int)y);
     196    }
     197
    196198    /**
    197199     * @param r
     
    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);
    236     }
    237 
    238     public Point getPoint(LatLon latlon) {
     237        return new Point2D.Double(x, y);
     238    }
     239
     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));
    245     }
     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());
     257    }
     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
     
    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);
    439     }
    440 
    441     /**
    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.
    445      * @param p the screen point
    446      * @param predicate this parameter imposes a condition on the returned object, e.g.
    447      *        give the nearest node that is tagged.
    448      */
    449     public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
     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>>();
    450470        DataSet ds = getCurrentDataSet();
    451         if (ds == null)
    452             return null;
    453 
    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;
    470             }
    471         }
    472         return minPrimitive;
    473     }
    474 
    475     /**
    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.
    481      */
    482     public final List<WaySegment> getNearestWaySegments(Point p, Predicate<OsmPrimitive> predicate) {
    483         TreeMap<Double, List<WaySegment>> nearest = new TreeMap<Double, List<WaySegment>>();
    484         DataSet ds = getCurrentDataSet();
    485         if (ds == null)
    486             return null;
    487 
    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;
    500                     continue;
    501                 }
    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;
     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);
    512486                    }
    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);
    519                     }
    520                     l.add(new WaySegment(w, i));
    521                 }
    522 
    523                 lastN = n;
    524             }
    525         }
    526         ArrayList<WaySegment> nearestList = new ArrayList<WaySegment>();
    527         for (List<WaySegment> wss : nearest.values()) {
    528             nearestList.addAll(wss);
    529         }
    530         return nearestList;
    531     }
    532 
    533     /**
    534      * @return the nearest way segment to the screen point given that is not
    535      * in ignore.
    536      *
    537      * @param p the point for which to search the nearest segment.
    538      * @param ignore a collection of segments which are not to be returned.
    539      * @param predicate the returned object has to fulfill certain properties.
    540      * May be null.
    541      */
    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;
    547         if (ignore != null) {
    548             nearest.removeAll(ignore);
    549         }
    550         return nearest.isEmpty() ? null : nearest.get(0);
    551     }
    552 
    553     /**
    554      * @return the nearest way segment to the screen point given.
    555      */
    556     public final WaySegment getNearestWaySegment(Point p, Predicate<OsmPrimitive> predicate) {
    557         return getNearestWaySegment(p, null, predicate);
    558     }
    559 
    560     @Deprecated
    561     public final Way getNearestWay(Point p) {
    562         return getNearestWay(p, OsmPrimitive.isUsablePredicate);
    563     }
    564 
    565     /**
    566      * @return the nearest way to the screen point given.
    567      */
    568     public final Way getNearestWay(Point p, Predicate<OsmPrimitive> predicate) {
    569         WaySegment nearestWaySeg = getNearestWaySegment(p, predicate);
    570         return nearestWaySeg == null ? null : nearestWaySeg.way;
    571     }
    572 
    573     /**
    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      *
    583      * @param p The point on screen.
    584      * @param predicate the returned object has to fulfill certain properties.
    585      * @return  The primitive that is nearest to the point p.
    586      */
    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;
    594     }
    595 
    596     /**
    597      * Returns a singleton of the nearest object, or else an empty collection.
    598      */
    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);
    604     }
    605 
    606     /**
    607      * @return A list of all objects that are nearest to
    608      * the mouse.
    609      *
    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.
    613      */
    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;
    629                 }
    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);
    638                     break;
    639                 }
    640                 lastN = n;
    641             }
    642         }
    643         for (Node n : ds.searchNodes(getSnapDistanceBBox(p))) {
    644             if (n.isUsable()
    645                     && getPoint(n).distanceSq(p) < snapDistanceSq) {
    646                 nearest.add(n);
    647             }
    648         }
    649         return nearest.isEmpty() ? null : nearest;
    650     }
    651 
    652     /**
    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.
    659      */
    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;
    665 
    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             }
    672         }
    673         return nearest.isEmpty() ? null : nearest;
    674     }
    675 
    676     /**
    677      * @return the nearest nodes to the screen point given that is not
    678      * in ignore.
     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.
    679503     *
    680504     * @param p the point for which to search the nearest segment.
    681505     * @param ignore a collection of nodes which are not to be returned.
    682506     * @param predicate the returned objects have to fulfill certain properties.
    683      * May be null.
    684      */
    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;
     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);
     556    }
     557
     558    /**
     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     *
     575     * @param p the screen point
     576     * @param predicate this parameter imposes a condition on the returned object, e.g.
     577     *        give the nearest node that is tagged.
     578     */
     579    public final Node getNearestNode(Point p, Predicate<OsmPrimitive> predicate) {
     580        Node n = null;
     581
     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                }
     599            }
     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));
     604        }
     605
     606        return n;
     607    }
     608
     609    @Deprecated
     610    public final Node getNearestNode(Point p) {
     611        return getNearestNode(p, OsmPrimitive.isUsablePredicate);
     612    }
     613
     614    /**
     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.
     621     */
     622    private Map<Double, List<WaySegment>> getNearestWaySegmentsImpl(Point p,
     623            Predicate<OsmPrimitive> predicate) {
     624        Map<Double, List<WaySegment>> nearestMap = new TreeMap<Double, List<WaySegment>>();
     625        DataSet ds = getCurrentDataSet();
     626
     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)) {
     633                    continue;
     634                }
     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;
     641                    }
     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));
     673                    }
     674
     675                    lastN = n;
     676                }
     677            }
     678        }
     679
     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);
     705            }
     706            nearestList.addAll(unselected);
     707            unselected.clear();
     708        }
    688709        if (ignore != null) {
    689             nearest.removeAll(ignore);
    690         }
    691         return nearest.isEmpty() ? null : nearest;
     710            nearestList.removeAll(ignore);
     711        }
     712
     713        return nearestList;
     714    }
     715
     716    /**
     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)
     735     *
     736     * @param p the point for which to search the nearest segment.
     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.
     769     * @param predicate the returned object has to fulfill certain properties.
     770     */
     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        }
     783        if (ignore != null) {
     784            nearestList.removeAll(ignore);
     785        }
     786
     787        return nearestList;
     788    }
     789
     790    /**
     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.
     800     */
     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;
     818    }
     819
     820    @Deprecated
     821    public final Way getNearestWay(Point p) {
     822        return getNearestWay(p, OsmPrimitive.isUsablePredicate);
     823    }
     824
     825    /**
     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.
     845     */
     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;
     863    }
     864
     865    /**
     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     *
     873     * @param p The point on screen.
     874     * @param predicate the returned object has to fulfill certain properties.
     875     */
     876    public final List<OsmPrimitive> getNearestNodesOrWays(Point p, Predicate<OsmPrimitive> predicate) {
     877        return getNearestNodesOrWays(p, null, predicate);
     878    }
     879
     880    /**
     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
     888     */
     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;
     901    }
     902
     903    /**
     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)
     923     *
     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.
     927     */
     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;
     948                }
     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);
     958                    break;
     959                }
     960            }
     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                }
     990            }
     991        }
     992
     993        return osm;
     994    }
     995
     996    /**
     997     * Convenience method to {@link #getNearestNodeOrWay(Point, Predicate, boolean)}.
     998     *
     999     * @return The nearest primitive to point p.
     1000     */
     1001    public final OsmPrimitive getNearestNodeOrWay(Point p, Predicate<OsmPrimitive> predicate) {
     1002        return getNearestNodeOrWay(p, predicate, false);
     1003    }
     1004
     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);
     1030        }
     1031        return 0d;
     1032    }
     1033
     1034    /**
     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.
     1083     */
     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        }
     1099        if (ignore != null) {
     1100            nearestList.removeAll(ignore);
     1101        }
     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
  • trunk/src/org/openstreetmap/josm/gui/SelectionManager.java

    r3198 r3594  
    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.
     
    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    }
     
    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);
  • trunk/src/org/openstreetmap/josm/gui/preferences/PrefJPanel.java

    r3247 r3594  
    195195        //shortcutTable.setFillsViewportHeight(true); Java 1.6
    196196        shortcutTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
     197        shortcutTable.setAutoCreateRowSorter(true);
    197198        listScrollPane.setViewportView(shortcutTable);
    198199
Note: See TracChangeset for help on using the changeset viewer.