Changeset 5370 in josm


Ignore:
Timestamp:
Jul 28, 2012 9:59:43 AM (10 months ago)
Author:
akks
Message:

see #5341: faster nodes moving (by JB) beginUpdate/endUpdate) + big refactoring of SelectAction.java
behavior of Select tool should NOT change (just more structured code), see #7888

File:
1 edited

Legend:

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

    r5265 r5370  
    196196    } 
    197197 
     198     /** 
     199     * This is called whenever the keyboard modifier status changes 
     200     */ 
     201    public void eventDispatched(AWTEvent e) { 
     202        if(oldEvent == null) 
     203            return; 
     204        // We don't have a mouse event, so we pass the old mouse event but the 
     205        // new modifiers. 
     206        if(giveUserFeedback(oldEvent, ((InputEvent) e).getModifiers())) { 
     207            mv.repaint(); 
     208        } 
     209    } 
     210 
     211    /** 
     212     * handles adding highlights and updating the cursor for the given mouse event. 
     213     * Please note that the highlighting for merging while moving is handled via mouseDragged. 
     214     * @param MouseEvent which should be used as base for the feedback 
     215     * @return true if repaint is required 
     216     */ 
     217    private boolean giveUserFeedback(MouseEvent e) { 
     218        return giveUserFeedback(e, e.getModifiers()); 
     219    } 
     220 
     221    /** 
     222     * handles adding highlights and updating the cursor for the given mouse event. 
     223     * Please note that the highlighting for merging while moving is handled via mouseDragged. 
     224     * @param MouseEvent which should be used as base for the feedback 
     225     * @param define custom keyboard modifiers if the ones from MouseEvent are outdated or similar 
     226     * @return true if repaint is required 
     227     */ 
     228    private boolean giveUserFeedback(MouseEvent e, int modifiers) { 
     229        Collection<OsmPrimitive> c = MapView.asColl( 
     230                mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, true)); 
     231 
     232        updateKeyModifiers(modifiers); 
     233        determineMapMode(!c.isEmpty()); 
     234 
     235        HashSet<OsmPrimitive> newHighlights = new HashSet<OsmPrimitive>(); 
     236 
     237        virtualManager.clear(); 
     238        if(mode == Mode.move && virtualManager.setupVirtual(e.getPoint())) { 
     239            DataSet ds = getCurrentDataSet(); 
     240            if (ds != null && drawTargetHighlight) { 
     241                ds.setHighlightedVirtualNodes(virtualManager.virtualWays); 
     242            } 
     243            mv.setNewCursor(SelectActionCursor.virtual_node.cursor(), this); 
     244            // don't highlight anything else if a virtual node will be 
     245            return repaintIfRequired(newHighlights); 
     246        } 
     247 
     248        mv.setNewCursor(getCursor(c), this); 
     249 
     250        // return early if there can't be any highlights 
     251        if(!drawTargetHighlight || mode != Mode.move || c.isEmpty()) 
     252            return repaintIfRequired(newHighlights); 
     253 
     254        // CTRL toggles selection, but if while dragging CTRL means merge 
     255        final boolean isToggleMode = ctrl && !dragInProgress(); 
     256        for(OsmPrimitive x : c) { 
     257            // only highlight primitives that will change the selection 
     258            // when clicked. I.e. don't highlight selected elements unless 
     259            // we are in toggle mode. 
     260            if(isToggleMode || !x.isSelected()) { 
     261                newHighlights.add(x); 
     262            } 
     263        } 
     264        return repaintIfRequired(newHighlights); 
     265    } 
     266     
    198267    /** 
    199268     * works out which cursor should be displayed for most of SelectAction's 
     
    207276        switch(mode) { 
    208277        case move: 
    209             if(virtualNode != null) { 
     278            if(virtualManager.hasVirtualNode()) { 
    210279                c = "virtual_node"; 
    211280                break; 
     
    294363        return needsRepaint; 
    295364    } 
    296  
    297     /** 
    298      * handles adding highlights and updating the cursor for the given mouse event. 
    299      * Please note that the highlighting for merging while moving is handled via mouseDragged. 
    300      * @param MouseEvent which should be used as base for the feedback 
    301      * @return true if repaint is required 
    302      */ 
    303     private boolean giveUserFeedback(MouseEvent e) { 
    304         return giveUserFeedback(e, e.getModifiers()); 
    305     } 
    306  
    307     /** 
    308      * handles adding highlights and updating the cursor for the given mouse event. 
    309      * Please note that the highlighting for merging while moving is handled via mouseDragged. 
    310      * @param MouseEvent which should be used as base for the feedback 
    311      * @param define custom keyboard modifiers if the ones from MouseEvent are outdated or similar 
    312      * @return true if repaint is required 
    313      */ 
    314     private boolean giveUserFeedback(MouseEvent e, int modifiers) { 
     365     
     366     /** 
     367     * Look, whether any object is selected. If not, select the nearest node. 
     368     * If there are no nodes in the dataset, do nothing. 
     369     * 
     370     * If the user did not press the left mouse button, do nothing. 
     371     * 
     372     * Also remember the starting position of the movement and change the mouse 
     373     * cursor to movement. 
     374     */ 
     375    @Override 
     376    public void mousePressed(MouseEvent e) { 
     377        // return early 
     378        if (!mv.isActiveLayerVisible() || !(Boolean) this.getValue("active") || (mouseDownButton = e.getButton()) != MouseEvent.BUTTON1) 
     379            return; 
     380         
     381        // left-button mouse click only is processed here 
     382         
     383        // request focus in order to enable the expected keyboard shortcuts 
     384        mv.requestFocus(); 
     385 
     386        // update which modifiers are pressed (shift, alt, ctrl) 
     387        updateKeyModifiers(e); 
     388 
     389        // We don't want to change to draw tool if the user tries to (de)select 
     390        // stuff but accidentally clicks in an empty area when selection is empty 
     391        cancelDrawMode = (shift || ctrl); 
     392        didMouseDrag = false; 
     393        initialMoveThresholdExceeded = false; 
     394        mouseDownTime = System.currentTimeMillis(); 
     395        lastMousePos = e.getPoint(); 
     396 
     397        // primitives under cursor are stored in c collection 
    315398        Collection<OsmPrimitive> c = MapView.asColl( 
    316399                mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, true)); 
    317400 
    318         updateKeyModifiers(modifiers); 
    319401        determineMapMode(!c.isEmpty()); 
    320  
    321         HashSet<OsmPrimitive> newHighlights = new HashSet<OsmPrimitive>(); 
    322  
    323         virtualWays.clear(); 
    324         virtualNode = null; 
    325         if(mode == Mode.move && setupVirtual(e)) { 
    326             DataSet ds = getCurrentDataSet(); 
    327             if (ds != null && drawTargetHighlight) { 
    328                 ds.setHighlightedVirtualNodes(virtualWays); 
    329             } 
    330             mv.setNewCursor(SelectActionCursor.virtual_node.cursor(), this); 
    331             // don't highlight anything else if a virtual node will be 
    332             return repaintIfRequired(newHighlights); 
    333         } 
    334  
    335         mv.setNewCursor(getCursor(c), this); 
    336  
    337         // return early if there can't be any highlights 
    338         if(!drawTargetHighlight || mode != Mode.move || c.isEmpty()) 
    339             return repaintIfRequired(newHighlights); 
    340  
    341         // CTRL toggles selection, but if while dragging CTRL means merge 
    342         final boolean isToggleMode = ctrl && !dragInProgress(); 
    343         for(OsmPrimitive x : c) { 
    344             // only highlight primitives that will change the selection 
    345             // when clicked. I.e. don't highlight selected elements unless 
    346             // we are in toggle mode. 
    347             if(isToggleMode || !x.isSelected()) { 
    348                 newHighlights.add(x); 
    349             } 
    350         } 
    351         return repaintIfRequired(newHighlights); 
    352     } 
    353  
    354     /** 
    355      * This is called whenever the keyboard modifier status changes 
    356      */ 
    357     public void eventDispatched(AWTEvent e) { 
    358         if(oldEvent == null) 
    359             return; 
    360         // We don't have a mouse event, so we pass the old mouse event but the 
    361         // new modifiers. 
    362         if(giveUserFeedback(oldEvent, ((InputEvent) e).getModifiers())) { 
     402         
     403        switch(mode) { 
     404        case rotate: 
     405        case scale: 
     406            if (getCurrentDataSet().getSelected().isEmpty()) { 
     407                getCurrentDataSet().setSelected(c); 
     408            } 
     409 
     410            // Mode.select redraws when selectPrims is called 
     411            // Mode.move   redraws when mouseDragged is called 
     412            // Mode.rotate redraws here 
     413            // Mode.scale redraws here 
     414            break; 
     415        case move: 
     416            if (!cancelDrawMode && c.iterator().next() instanceof Way) { 
     417                virtualManager.setupVirtual(e.getPoint()); 
     418            } 
     419            
     420            selectPrims(cycleManager.cycleSetup(c, e.getPoint()), false, false); 
     421            break; 
     422        case select: 
     423        default: 
     424            selectionManager.register(mv, lassoMode); 
     425            selectionManager.mousePressed(e); 
     426            break; 
     427        } 
     428        giveUserFeedback(e); 
     429        mv.repaint(); 
     430        updateStatusLine(); 
     431    } 
     432     
     433    @Override 
     434    public void mouseMoved(MouseEvent e) { 
     435        // Mac OSX simulates with  ctrl + mouse 1  the second mouse button hence no dragging events get fired. 
     436        if ((Main.platform instanceof PlatformHookOsx) && (mode == Mode.rotate || mode == Mode.scale)) { 
     437            mouseDragged(e); 
     438            return; 
     439        } 
     440        oldEvent = e; 
     441        if(giveUserFeedback(e)) { 
    363442            mv.repaint(); 
    364443        } 
    365444    } 
    366  
     445     
    367446    /** 
    368447     * If the left mouse button is pressed, move all currently selected 
     
    398477            // highlight it and adjust the cursor accordingly. 
    399478            final boolean canMerge = ctrl && !getCurrentDataSet().getSelectedNodes().isEmpty(); 
    400             final OsmPrimitive p = canMerge ? (OsmPrimitive)findNodeToMergeTo(e) : null; 
     479            final OsmPrimitive p = canMerge ? (OsmPrimitive)findNodeToMergeTo(e.getPoint()) : null; 
    401480            boolean needsRepaint = removeHighlighting(); 
    402481            if(p != null) { 
     
    440519            return; 
    441520 
    442         if (virtualWays.size() > 0) { 
    443             Collection<Command> virtualCmds = new LinkedList<Command>(); 
    444             virtualCmds.add(new AddCommand(virtualNode)); 
    445             for (WaySegment virtualWay : virtualWays) { 
    446                 Way w = virtualWay.way; 
    447                 Way wnew = new Way(w); 
    448                 wnew.addNode(virtualWay.lowerIndex + 1, virtualNode); 
    449                 virtualCmds.add(new ChangeCommand(w, wnew)); 
    450             } 
    451             virtualCmds.add(new MoveCommand(virtualNode, dx, dy)); 
    452             String text = trn("Add and move a virtual new node to way", 
    453                     "Add and move a virtual new node to {0} ways", virtualWays.size(), 
    454                     virtualWays.size()); 
    455             Main.main.undoRedo.add(new SequenceCommand(text, virtualCmds)); 
    456             getCurrentDataSet().setSelected(Collections.singleton((OsmPrimitive) virtualNode)); 
    457             virtualWays.clear(); 
    458             virtualNode = null; 
     521        if (virtualManager.hasVirtualWays()) { 
     522            virtualManager.processVirtualNodeMovements(dx, dy); 
    459523        } else { 
    460             // Currently we support only transformations which do not affect relations. 
    461             // So don't add them in the first place to make handling easier 
    462             Collection<OsmPrimitive> selection = getCurrentDataSet().getSelectedNodesAndWays(); 
    463             Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection); 
    464  
    465             // for these transformations, having only one node makes no sense - quit silently 
    466             if (affectedNodes.size() < 2 && (mode == Mode.rotate || mode == Mode.scale)) 
    467                 return; 
    468  
    469             Command c = !Main.main.undoRedo.commands.isEmpty() 
    470                     ? Main.main.undoRedo.commands.getLast() : null; 
    471             if (c instanceof SequenceCommand) { 
    472                 c = ((SequenceCommand) c).getLastCommand(); 
    473             } 
    474  
    475             if (mode == Mode.move) { 
    476                 if (c instanceof MoveCommand && affectedNodes.equals(((MoveCommand) c).getParticipatingPrimitives())) { 
    477                     ((MoveCommand) c).moveAgain(dx, dy); 
    478                 } else { 
    479                     Main.main.undoRedo.add( 
    480                             c = new MoveCommand(selection, dx, dy)); 
    481                 } 
    482  
    483                 for (Node n : affectedNodes) { 
    484                     if (n.getCoor().isOutSideWorld()) { 
    485                         // Revert move 
    486                         ((MoveCommand) c).moveAgain(-dx, -dy); 
    487  
    488                         JOptionPane.showMessageDialog( 
    489                                 Main.parent, 
    490                                 tr("Cannot move objects outside of the world."), 
    491                                 tr("Warning"), 
    492                                 JOptionPane.WARNING_MESSAGE); 
    493                         mv.setNewCursor(cursor, this); 
    494                         return; 
    495                     } 
    496                 } 
    497             } else if (mode == Mode.rotate) { 
    498                 if (c instanceof RotateCommand && affectedNodes.equals(((RotateCommand) c).getTransformedNodes())) { 
    499                     ((RotateCommand) c).handleEvent(currentEN); 
    500                 } else { 
    501                     Main.main.undoRedo.add(new RotateCommand(selection, currentEN)); 
    502                 } 
    503             } else if (mode == Mode.scale) { 
    504                 if (c instanceof ScaleCommand && affectedNodes.equals(((ScaleCommand) c).getTransformedNodes())) { 
    505                     ((ScaleCommand) c).handleEvent(currentEN); 
    506                 } else { 
    507                     Main.main.undoRedo.add(new ScaleCommand(selection, currentEN)); 
    508                 } 
    509             } 
     524            if (!updateCommandWhileDragging(dx, dy, currentEN)) return; 
    510525        } 
    511526 
     
    518533    } 
    519534 
    520     @Override 
    521     public void mouseMoved(MouseEvent e) { 
    522         // Mac OSX simulates with  ctrl + mouse 1  the second mouse button hence no dragging events get fired. 
    523         if ((Main.platform instanceof PlatformHookOsx) && (mode == Mode.rotate || mode == Mode.scale)) { 
    524             mouseDragged(e); 
    525             return; 
    526         } 
    527         oldEvent = e; 
    528         if(giveUserFeedback(e)) { 
    529             mv.repaint(); 
    530         } 
    531     } 
     535     
    532536 
    533537    @Override 
     
    538542    } 
    539543 
    540     /** returns true whenever elements have been grabbed and moved (i.e. the initial 
    541      * thresholds have been exceeded) and is still in progress (i.e. mouse button 
    542      * still pressed) 
    543      */ 
    544     final private boolean dragInProgress() { 
    545         return didMouseDrag && startingDraggingPos != null; 
    546     } 
    547  
    548     private Node virtualNode = null; 
    549     private Collection<WaySegment> virtualWays = new LinkedList<WaySegment>(); 
    550  
    551     /** 
    552      * Calculate a virtual node if there is enough visual space to draw a crosshair 
    553      * node and the middle of a way segment is clicked.  If the user drags the 
    554      * crosshair node, it will be added to all ways in <code>virtualWays</code>. 
    555      *  
    556      * @param e contains the point clicked 
    557      * @return whether <code>virtualNode</code> and <code>virtualWays</code> were setup. 
    558      */ 
    559     private boolean setupVirtual(MouseEvent e) { 
    560         if (Main.pref.getInteger("mappaint.node.virtual-size", 8) > 0) { 
    561             int virtualSnapDistSq = Main.pref.getInteger("mappaint.node.virtual-snap-distance", 8); 
    562             int virtualSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70); 
    563             virtualSnapDistSq *= virtualSnapDistSq; 
    564  
    565             Collection<WaySegment> selVirtualWays = new LinkedList<WaySegment>(); 
    566             Pair<Node, Node> vnp = null, wnp = new Pair<Node, Node>(null, null); 
    567             Point p = e.getPoint(); 
    568             Way w = null; 
    569  
    570             for (WaySegment ws : mv.getNearestWaySegments(p, OsmPrimitive.isSelectablePredicate)) { 
    571                 w = ws.way; 
    572  
    573                 Point2D p1 = mv.getPoint2D(wnp.a = w.getNode(ws.lowerIndex)); 
    574                 Point2D p2 = mv.getPoint2D(wnp.b = w.getNode(ws.lowerIndex + 1)); 
    575                 if (WireframeMapRenderer.isLargeSegment(p1, p2, virtualSpace)) { 
    576                     Point2D pc = new Point2D.Double((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2); 
    577                     if (p.distanceSq(pc) < virtualSnapDistSq) { 
    578                         // Check that only segments on top of each other get added to the 
    579                         // virtual ways list. Otherwise ways that coincidentally have their 
    580                         // virtual node at the same spot will be joined which is likely unwanted 
    581                         Pair.sort(wnp); 
    582                         if (vnp == null) { 
    583                             vnp = new Pair<Node, Node>(wnp.a, wnp.b); 
    584                             virtualNode = new Node(mv.getLatLon(pc.getX(), pc.getY())); 
    585                         } 
    586                         if (vnp.equals(wnp)) { 
    587                             (w.isSelected() ? selVirtualWays : virtualWays).add(ws); 
    588                         } 
    589                     } 
    590                 } 
    591             } 
    592  
    593             if (!selVirtualWays.isEmpty()) { 
    594                 virtualWays = selVirtualWays; 
    595             } 
    596         } 
    597  
    598         return !virtualWays.isEmpty(); 
    599     } 
    600     private Collection<OsmPrimitive> cycleList = Collections.emptyList(); 
    601     private boolean cyclePrims = false; 
    602     private OsmPrimitive cycleStart = null; 
    603  
    604     /** 
    605      *  
    606      * @param osm nearest primitive found by simple method 
    607      * @param e 
    608      * @return 
    609      */ 
    610     private Collection<OsmPrimitive> cycleSetup(Collection<OsmPrimitive> single, MouseEvent e) { 
    611         OsmPrimitive osm = null; 
    612  
    613         if (single != null && !single.isEmpty()) { 
    614             osm = single.iterator().next(); 
    615  
    616             Point p = e.getPoint(); 
    617             boolean waitForMouseUp = Main.pref.getBoolean("mappaint.select.waits-for-mouse-up", false); 
    618             updateKeyModifiers(e); 
    619             alt = alt || Main.pref.getBoolean("selectaction.cycles.multiple.matches", false); 
    620  
    621             if (!alt) { 
    622                 cycleList = MapView.asColl(osm); 
    623  
    624                 if (waitForMouseUp) { 
    625                     // prefer a selected nearest node or way, if possible 
    626                     osm = mv.getNearestNodeOrWay(p, OsmPrimitive.isSelectablePredicate, true); 
    627                 } 
    628             } else { 
    629                 cycleList = mv.getAllNearest(p, OsmPrimitive.isSelectablePredicate); 
    630  
    631                 if (cycleList.size() > 1) { 
    632                     cyclePrims = false; 
    633  
    634                     OsmPrimitive old = osm; 
    635                     for (OsmPrimitive o : cycleList) { 
    636                         if (o.isSelected()) { 
    637                             cyclePrims = true; 
    638                             osm = o; 
    639                             break; 
    640                         } 
    641                     } 
    642  
    643                     // special case:  for cycle groups of 2, we can toggle to the 
    644                     // true nearest primitive on mousePressed right away 
    645                     if (cycleList.size() == 2 && !waitForMouseUp) { 
    646                         if (!(osm.equals(old) || osm.isNew() || ctrl)) { 
    647                             cyclePrims = false; 
    648                             osm = old; 
    649                         } // else defer toggling to mouseRelease time in those cases: 
    650                         /* 
    651                          * osm == old        -- the true nearest node is the selected one 
    652                          * osm is a new node -- do not break unglue ways in ALT mode 
    653                          * ctrl is pressed   -- ctrl generally works on mouseReleased 
    654                          */ 
    655                     } 
    656                 } 
    657             } 
    658         } 
    659  
    660         return MapView.asColl(osm); 
    661     } 
    662  
    663     /** 
    664      * sets the mapmode according to key modifiers and if there are any 
    665      * selectables nearby. Everything has to be pre-determined for this 
    666      * function; its main purpose is to centralize what the modifiers do. 
    667      * @param hasSelectionNearby 
    668      */ 
    669     private void determineMapMode(boolean hasSelectionNearby) { 
    670         if (shift && ctrl) { 
    671             mode = Mode.rotate; 
    672         } else if (alt && ctrl) { 
    673             mode = Mode.scale; 
    674         } else if (hasSelectionNearby || dragInProgress()) { 
    675             mode = Mode.move; 
    676         } else { 
    677             mode = Mode.select; 
    678         } 
    679     } 
    680  
    681     /** 
    682      * Look, whether any object is selected. If not, select the nearest node. 
    683      * If there are no nodes in the dataset, do nothing. 
    684      * 
    685      * If the user did not press the left mouse button, do nothing. 
    686      * 
    687      * Also remember the starting position of the movement and change the mouse 
    688      * cursor to movement. 
    689      */ 
    690     @Override 
    691     public void mousePressed(MouseEvent e) { 
    692         // return early 
    693         if (!mv.isActiveLayerVisible() || !(Boolean) this.getValue("active") || (mouseDownButton = e.getButton()) != MouseEvent.BUTTON1) 
    694             return; 
    695  
    696         // request focus in order to enable the expected keyboard shortcuts 
    697         mv.requestFocus(); 
    698  
    699         // update which modifiers are pressed (shift, alt, ctrl) 
    700         updateKeyModifiers(e); 
    701  
    702         // We don't want to change to draw tool if the user tries to (de)select 
    703         // stuff but accidentally clicks in an empty area when selection is empty 
    704         cancelDrawMode = (shift || ctrl); 
    705         didMouseDrag = false; 
    706         initialMoveThresholdExceeded = false; 
    707         mouseDownTime = System.currentTimeMillis(); 
    708         lastMousePos = e.getPoint(); 
    709  
    710         Collection<OsmPrimitive> c = MapView.asColl( 
    711                 mv.getNearestNodeOrWay(e.getPoint(), OsmPrimitive.isSelectablePredicate, true)); 
    712  
    713         determineMapMode(!c.isEmpty()); 
    714         switch(mode) { 
    715         case rotate: 
    716         case scale: 
    717             if (getCurrentDataSet().getSelected().isEmpty()) { 
    718                 getCurrentDataSet().setSelected(c); 
    719             } 
    720  
    721             // Mode.select redraws when selectPrims is called 
    722             // Mode.move   redraws when mouseDragged is called 
    723             // Mode.rotate redraws here 
    724             // Mode.scale redraws here 
    725             break; 
    726         case move: 
    727             if (!cancelDrawMode && c.iterator().next() instanceof Way) { 
    728                 setupVirtual(e); 
    729             } 
    730  
    731             selectPrims(cycleSetup(c, e), e, false, false); 
    732             break; 
    733         case select: 
    734         default: 
    735             selectionManager.register(mv, lassoMode); 
    736             selectionManager.mousePressed(e); 
    737             break; 
    738         } 
    739         giveUserFeedback(e); 
    740         mv.repaint(); 
    741         updateStatusLine(); 
    742     } 
    743  
     544     
    744545    @Override 
    745546    public void mouseReleased(MouseEvent e) { 
     
    763564            if (!didMouseDrag) { 
    764565                // only built in move mode 
    765                 virtualWays.clear(); 
    766                 virtualNode = null; 
    767  
     566                virtualManager.clear(); 
    768567                // do nothing if the click was to short too be recognized as a drag, 
    769568                // but the release position is farther than 10px away from the press position 
    770569                if (lastMousePos == null || lastMousePos.distanceSq(e.getPoint()) < 100) { 
    771                     selectPrims(cyclePrims(cycleList, e), e, true, false); 
     570                    updateKeyModifiers(e); 
     571                    selectPrims(cycleManager.cyclePrims(), true, false); 
    772572 
    773573                    // If the user double-clicked a node, change to draw mode 
     
    785585                } 
    786586            } else { 
    787                 int max = Main.pref.getInteger("warn.move.maxelements", 20), limit = max; 
    788                 for (OsmPrimitive osm : getCurrentDataSet().getSelected()) { 
    789                     if (osm instanceof Way) { 
    790                         limit -= ((Way) osm).getNodes().size(); 
    791                     } 
    792                     if ((limit -= 1) < 0) { 
    793                         break; 
    794                     } 
    795                 } 
    796                 if (limit < 0) { 
    797                     ExtendedDialog ed = new ExtendedDialog( 
    798                             Main.parent, 
    799                             tr("Move elements"), 
    800                             new String[]{tr("Move them"), tr("Undo move")}); 
    801                     ed.setButtonIcons(new String[]{"reorder.png", "cancel.png"}); 
    802                     ed.setContent(tr("You moved more than {0} elements. " + "Moving a large number of elements is often an error.\n" + "Really move them?", max)); 
    803                     ed.setCancelButton(2); 
    804                     ed.toggleEnable("movedManyElements"); 
    805                     ed.showDialog(); 
    806  
    807                     if (ed.getValue() != 1) { 
    808                         Main.main.undoRedo.undo(); 
    809                     } 
    810                 } else { 
    811                     mergePrims(e); 
    812                 } 
    813                 getCurrentDataSet().fireSelectionChanged(); 
     587                confirmOrUndoMovement(e); 
    814588            } 
    815589        } 
     
    829603    } 
    830604 
     605    @Override 
    831606    public void selectionEnded(Rectangle r, MouseEvent e) { 
    832607        updateKeyModifiers(e); 
    833608        mv.repaint(); 
    834         selectPrims(selectionManager.getSelectedObjects(alt), e, true, true); 
    835     } 
    836  
    837     /** 
    838      * Modifies current selection state and returns the next element in a 
    839      * selection cycle given by <code>prims</code>. 
    840      * @param prims the primitives that form the selection cycle 
    841      * @param mouse event 
    842      * @return the next element of cycle list <code>prims</code>. 
    843      */ 
    844     private Collection<OsmPrimitive> cyclePrims(Collection<OsmPrimitive> prims, MouseEvent e) { 
    845         OsmPrimitive nxt = null; 
    846  
    847         if (prims.size() > 1) { 
     609        selectPrims(selectionManager.getSelectedObjects(alt), true, true); 
     610    } 
     611 
     612    /** 
     613     * sets the mapmode according to key modifiers and if there are any 
     614     * selectables nearby. Everything has to be pre-determined for this 
     615     * function; its main purpose is to centralize what the modifiers do. 
     616     * @param hasSelectionNearby 
     617     */ 
     618    private void determineMapMode(boolean hasSelectionNearby) { 
     619        if (shift && ctrl) { 
     620            mode = Mode.rotate; 
     621        } else if (alt && ctrl) { 
     622            mode = Mode.scale; 
     623        } else if (hasSelectionNearby || dragInProgress()) { 
     624            mode = Mode.move; 
     625        } else { 
     626            mode = Mode.select; 
     627        } 
     628    } 
     629     
     630    /** returns true whenever elements have been grabbed and moved (i.e. the initial 
     631     * thresholds have been exceeded) and is still in progress (i.e. mouse button 
     632     * still pressed) 
     633     */ 
     634    final private boolean dragInProgress() { 
     635        return didMouseDrag && startingDraggingPos != null; 
     636    } 
     637 
     638     
     639    /** 
     640     * Create or update data modfication command whle dragging mouse - implementation of  
     641     * continuous moving, scaling and rotation 
     642     * @param dx, @param dy - mouse displacement 
     643     * @param currentEN - 
     644     * @return  
     645     */ 
     646    private boolean updateCommandWhileDragging(double dx, double dy, EastNorth currentEN) { 
     647        // Currently we support only transformations which do not affect relations. 
     648        // So don't add them in the first place to make handling easier 
     649        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelectedNodesAndWays(); 
     650        Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection); 
     651        // for these transformations, having only one node makes no sense - quit silently 
     652        if (affectedNodes.size() < 2 && (mode == Mode.rotate || mode == Mode.scale)) { 
     653            return false; 
     654        } 
     655        Command c = !Main.main.undoRedo.commands.isEmpty() 
     656                ? Main.main.undoRedo.commands.getLast() : null; 
     657        if (c instanceof SequenceCommand) { 
     658            c = ((SequenceCommand) c).getLastCommand(); 
     659        } 
     660        if (mode == Mode.move) { 
     661            getCurrentDataSet().beginUpdate(); 
     662            if (c instanceof MoveCommand && affectedNodes.equals(((MoveCommand) c).getParticipatingPrimitives())) { 
     663                ((MoveCommand) c).moveAgain(dx, dy); 
     664            } else { 
     665                Main.main.undoRedo.add( 
     666                        c = new MoveCommand(selection, dx, dy)); 
     667            } 
     668            getCurrentDataSet().endUpdate(); 
     669            for (Node n : affectedNodes) { 
     670                if (n.getCoor().isOutSideWorld()) { 
     671                    // Revert move 
     672                    ((MoveCommand) c).moveAgain(-dx, -dy); 
     673                    JOptionPane.showMessageDialog( 
     674                            Main.parent, 
     675                            tr("Cannot move objects outside of the world."), 
     676                            tr("Warning"), 
     677                            JOptionPane.WARNING_MESSAGE); 
     678                    mv.setNewCursor(cursor, this); 
     679                    return false; 
     680                } 
     681            } 
     682        } else if (mode == Mode.rotate) { 
     683            getCurrentDataSet().beginUpdate(); 
     684            if (c instanceof RotateCommand && affectedNodes.equals(((RotateCommand) c).getTransformedNodes())) { 
     685                ((RotateCommand) c).handleEvent(currentEN); 
     686            } else { 
     687                Main.main.undoRedo.add(new RotateCommand(selection, currentEN)); 
     688            } 
     689            getCurrentDataSet().endUpdate(); 
     690        } else if (mode == Mode.scale) { 
     691            getCurrentDataSet().beginUpdate(); 
     692            if (c instanceof ScaleCommand && affectedNodes.equals(((ScaleCommand) c).getTransformedNodes())) { 
     693                ((ScaleCommand) c).handleEvent(currentEN); 
     694            } else { 
     695                Main.main.undoRedo.add(new ScaleCommand(selection, currentEN)); 
     696            } 
     697            getCurrentDataSet().endUpdate(); 
     698        } 
     699        return true; 
     700    } 
     701     
     702    private void confirmOrUndoMovement(MouseEvent e) { 
     703        int max = Main.pref.getInteger("warn.move.maxelements", 20), limit = max; 
     704        for (OsmPrimitive osm : getCurrentDataSet().getSelected()) { 
     705            if (osm instanceof Way) { 
     706                limit -= ((Way) osm).getNodes().size(); 
     707            } 
     708            if ((limit -= 1) < 0) { 
     709                break; 
     710            } 
     711        } 
     712        if (limit < 0) { 
     713            ExtendedDialog ed = new ExtendedDialog( 
     714                    Main.parent, 
     715                    tr("Move elements"), 
     716                    new String[]{tr("Move them"), tr("Undo move")}); 
     717            ed.setButtonIcons(new String[]{"reorder.png", "cancel.png"}); 
     718            ed.setContent(tr("You moved more than {0} elements. " + "Moving a large number of elements is often an error.\n" + "Really move them?", max)); 
     719            ed.setCancelButton(2); 
     720            ed.toggleEnable("movedManyElements"); 
     721            ed.showDialog(); 
     722 
     723            if (ed.getValue() != 1) { 
     724                Main.main.undoRedo.undo(); 
     725            } 
     726        } else { 
     727            // if small number of elements were moved, 
    848728            updateKeyModifiers(e); 
    849  
    850             DataSet ds = getCurrentDataSet(); 
    851             OsmPrimitive first = prims.iterator().next(), foundInDS = null; 
    852             nxt = first; 
    853  
    854             for (Iterator<OsmPrimitive> i = prims.iterator(); i.hasNext();) { 
    855                 if (cyclePrims && shift) { 
    856                     if (!(nxt = i.next()).isSelected()) { 
    857                         break; // take first primitive in prims list not in sel 
    858                     } 
    859                 } else { 
    860                     if ((nxt = i.next()).isSelected()) { 
    861                         foundInDS = nxt; 
    862                         if (cyclePrims || ctrl) { 
    863                             ds.clearSelection(foundInDS); 
    864                             nxt = i.hasNext() ? i.next() : first; 
    865                         } 
    866                         break; // take next primitive in prims list 
    867                     } 
    868                 } 
    869             } 
    870  
    871             if (ctrl) { 
    872                 // a member of prims was found in the current dataset selection 
    873                 if (foundInDS != null) { 
    874                     // mouse was moved to a different selection group w/ a previous sel 
    875                     if (!prims.contains(cycleStart)) { 
    876                         ds.clearSelection(prims); 
    877                         cycleStart = foundInDS; 
    878                     } else if (cycleStart.equals(nxt)) { 
    879                         // loop detected, insert deselect step 
    880                         ds.addSelected(nxt); 
    881                     } 
    882                 } else { 
    883                     // setup for iterating a sel group again or a new, different one.. 
    884                     nxt = (prims.contains(cycleStart)) ? cycleStart : first; 
    885                     cycleStart = nxt; 
    886                 } 
    887             } else { 
    888                 cycleStart = null; 
    889             } 
    890         } 
    891  
    892         // pass on prims, if it had less than 2 elements 
    893         return (nxt != null) ? MapView.asColl(nxt) : prims; 
    894     } 
    895  
     729            if (ctrl) mergePrims(e.getPoint()); 
     730        } 
     731        getCurrentDataSet().fireSelectionChanged(); 
     732    } 
     733     
    896734    /** Merges the selected nodes to the one closest to the given mouse position iff the control 
    897735     * key is pressed. If there is no such node, no action will be done and no error will be 
    898736     * reported. If there is, it will execute the merge and add it to the undo buffer. */ 
    899     final private void mergePrims(MouseEvent e) { 
    900         updateKeyModifiers(e); 
     737    final private void mergePrims(Point p) { 
    901738        Collection<Node> selNodes = getCurrentDataSet().getSelectedNodes(); 
    902         if (!ctrl || selNodes.isEmpty()) 
    903             return; 
    904  
    905         Node target = findNodeToMergeTo(e); 
     739        if (selNodes.isEmpty()) 
     740            return; 
     741 
     742        Node target = findNodeToMergeTo(p); 
    906743        if (target == null) 
    907744            return; 
     
    914751    /** tries to find a node to merge to when in move-merge mode for the current mouse 
    915752     * position. Either returns the node or null, if no suitable one is nearby. */ 
    916     final private Node findNodeToMergeTo(MouseEvent e) { 
    917         Collection<Node> target = mv.getNearestNodes(e.getPoint(), 
     753    final private Node findNodeToMergeTo(Point p) { 
     754        Collection<Node> target = mv.getNearestNodes(p, 
    918755                getCurrentDataSet().getSelectedNodes(), 
    919756                OsmPrimitive.isSelectablePredicate); 
     
    921758    } 
    922759 
    923     private void selectPrims(Collection<OsmPrimitive> prims, MouseEvent e, boolean released, boolean area) { 
    924         updateKeyModifiers(e); 
     760    private void selectPrims(Collection<OsmPrimitive> prims, boolean released, boolean area) { 
    925761        DataSet ds = getCurrentDataSet(); 
    926762 
     
    929765        // anything if about to drag the virtual node (i.e. !released) but continue if the 
    930766        // cursor is only released above a virtual node by accident (i.e. released). See #7018 
    931         if ((shift && ctrl) || (ctrl && !released) || (!virtualWays.isEmpty() && !released)) 
     767        if ((shift && ctrl) || (ctrl && !released) || (virtualManager.hasVirtualWays() && !released)) 
    932768            return; 
    933769 
     
    982818        this.lassoMode = lassoMode; 
    983819    } 
     820     
     821    CycleManager cycleManager = new CycleManager(); 
     822    VirtualManager virtualManager = new VirtualManager(); 
     823     
     824    private class CycleManager { 
     825 
     826        private Collection<OsmPrimitive> cycleList = Collections.emptyList(); 
     827        private boolean cyclePrims = false; 
     828        private OsmPrimitive cycleStart = null; 
     829 
     830        /** 
     831         * 
     832         * @param osm nearest primitive found by simple method 
     833         * @param e 
     834         * @return 
     835         */ 
     836        private Collection<OsmPrimitive> cycleSetup(Collection<OsmPrimitive> single, Point p) { 
     837            OsmPrimitive osm = null; 
     838 
     839            if (single != null && !single.isEmpty()) { 
     840                osm = single.iterator().next(); 
     841 
     842                // Point p = e.getPoint(); 
     843                boolean waitForMouseUp = Main.pref.getBoolean("mappaint.select.waits-for-mouse-up", false); 
     844//              updateKeyModifiers(e); // cycleSetup called only after updateModifiers ! 
     845                alt = alt || Main.pref.getBoolean("selectaction.cycles.multiple.matches", false); 
     846 
     847                if (!alt) { 
     848                    cycleList = MapView.asColl(osm); 
     849 
     850                    if (waitForMouseUp) { 
     851                        // prefer a selected nearest node or way, if possible 
     852                        osm = mv.getNearestNodeOrWay(p, OsmPrimitive.isSelectablePredicate, true); 
     853                    } 
     854                } else { 
     855                    cycleList = mv.getAllNearest(p, OsmPrimitive.isSelectablePredicate); 
     856 
     857                    if (cycleList.size() > 1) { 
     858                        cyclePrims = false; 
     859 
     860                        OsmPrimitive old = osm; 
     861                        for (OsmPrimitive o : cycleList) { 
     862                            if (o.isSelected()) { 
     863                                cyclePrims = true; 
     864                                osm = o; 
     865                                break; 
     866                            } 
     867                        } 
     868 
     869                        // special case:  for cycle groups of 2, we can toggle to the 
     870                        // true nearest primitive on mousePressed right away 
     871                        if (cycleList.size() == 2 && !waitForMouseUp) { 
     872                            if (!(osm.equals(old) || osm.isNew() || ctrl)) { 
     873                                cyclePrims = false; 
     874                                osm = old; 
     875                            } // else defer toggling to mouseRelease time in those cases: 
     876                            /* 
     877                             * osm == old -- the true nearest node is the 
     878                             * selected one osm is a new node -- do not break 
     879                             * unglue ways in ALT mode ctrl is pressed -- ctrl 
     880                             * generally works on mouseReleased 
     881                             */ 
     882                        } 
     883                    } 
     884                } 
     885            } 
     886 
     887            return MapView.asColl(osm); 
     888        } 
     889 
     890        /** 
     891         * Modifies current selection state and returns the next element in a 
     892         * selection cycle given by 
     893         * <code>prims</code>. 
     894         * 
     895         * @param prims the primitives that form the selection cycle 
     896         * @param mouse event 
     897         * @return the next element of cycle list 
     898         * <code>prims</code>. 
     899         */ 
     900        private Collection<OsmPrimitive> cyclePrims() { 
     901            Collection<OsmPrimitive> prims = cycleList; 
     902            OsmPrimitive nxt = null; 
     903 
     904            if (prims.size() > 1) { 
     905//                updateKeyModifiers(e); // already called before ! 
     906 
     907                DataSet ds = getCurrentDataSet(); 
     908                OsmPrimitive first = prims.iterator().next(), foundInDS = null; 
     909                nxt = first; 
     910 
     911                for (Iterator<OsmPrimitive> i = prims.iterator(); i.hasNext();) { 
     912                    if (cyclePrims && shift) { 
     913                        if (!(nxt = i.next()).isSelected()) { 
     914                            break; // take first primitive in prims list not in sel 
     915                        } 
     916                    } else { 
     917                        if ((nxt = i.next()).isSelected()) { 
     918                            foundInDS = nxt; 
     919                            if (cyclePrims || ctrl) { 
     920                                ds.clearSelection(foundInDS); 
     921                                nxt = i.hasNext() ? i.next() : first; 
     922                            } 
     923                            break; // take next primitive in prims list 
     924                        } 
     925                    } 
     926                } 
     927 
     928                if (ctrl) { 
     929                    // a member of prims was found in the current dataset selection 
     930                    if (foundInDS != null) { 
     931                        // mouse was moved to a different selection group w/ a previous sel 
     932                        if (!prims.contains(cycleStart)) { 
     933                            ds.clearSelection(prims); 
     934                            cycleStart = foundInDS; 
     935                        } else if (cycleStart.equals(nxt)) { 
     936                            // loop detected, insert deselect step 
     937                            ds.addSelected(nxt); 
     938                        } 
     939                    } else { 
     940                        // setup for iterating a sel group again or a new, different one.. 
     941                        nxt = (prims.contains(cycleStart)) ? cycleStart : first; 
     942                        cycleStart = nxt; 
     943                    } 
     944                } else { 
     945                    cycleStart = null; 
     946                } 
     947            } 
     948 
     949            // pass on prims, if it had less than 2 elements 
     950            return (nxt != null) ? MapView.asColl(nxt) : prims; 
     951        } 
     952    } 
     953     
     954    private class VirtualManager { 
     955 
     956        private Node virtualNode = null; 
     957        private Collection<WaySegment> virtualWays = new LinkedList<WaySegment>(); 
     958 
     959        /** 
     960         * Calculate a virtual node if there is enough visual space to draw a 
     961         * crosshair node and the middle of a way segment is clicked. If the 
     962         * user drags the crosshair node, it will be added to all ways in 
     963         * <code>virtualWays</code>. 
     964         * 
     965         * @param e contains the point clicked 
     966         * @return whether 
     967         * <code>virtualNode</code> and 
     968         * <code>virtualWays</code> were setup. 
     969         */ 
     970        private boolean setupVirtual(Point p) { 
     971            if (Main.pref.getInteger("mappaint.node.virtual-size", 8) > 0) { 
     972                int virtualSnapDistSq = Main.pref.getInteger("mappaint.node.virtual-snap-distance", 8); 
     973                int virtualSpace = Main.pref.getInteger("mappaint.node.virtual-space", 70); 
     974                virtualSnapDistSq *= virtualSnapDistSq; 
     975 
     976                Collection<WaySegment> selVirtualWays = new LinkedList<WaySegment>(); 
     977                Pair<Node, Node> vnp = null, wnp = new Pair<Node, Node>(null, null); 
     978 
     979                Way w = null; 
     980                for (WaySegment ws : mv.getNearestWaySegments(p, OsmPrimitive.isSelectablePredicate)) { 
     981                    w = ws.way; 
     982 
     983                    Point2D p1 = mv.getPoint2D(wnp.a = w.getNode(ws.lowerIndex)); 
     984                    Point2D p2 = mv.getPoint2D(wnp.b = w.getNode(ws.lowerIndex + 1)); 
     985                    if (WireframeMapRenderer.isLargeSegment(p1, p2, virtualSpace)) { 
     986                        Point2D pc = new Point2D.Double((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2); 
     987                        if (p.distanceSq(pc) < virtualSnapDistSq) { 
     988                            // Check that only segments on top of each other get added to the 
     989                            // virtual ways list. Otherwise ways that coincidentally have their 
     990                            // virtual node at the same spot will be joined which is likely unwanted 
     991                            Pair.sort(wnp); 
     992                            if (vnp == null) { 
     993                                vnp = new Pair<Node, Node>(wnp.a, wnp.b); 
     994                                virtualNode = new Node(mv.getLatLon(pc.getX(), pc.getY())); 
     995                            } 
     996                            if (vnp.equals(wnp)) { 
     997                                (w.isSelected() ? selVirtualWays : virtualWays).add(ws); 
     998                            } 
     999                        } 
     1000                    } 
     1001                } 
     1002 
     1003                if (!selVirtualWays.isEmpty()) { 
     1004                    virtualWays = selVirtualWays; 
     1005                } 
     1006            } 
     1007 
     1008            return !virtualWays.isEmpty(); 
     1009        } 
     1010 
     1011        private void processVirtualNodeMovements(double dx, double dy) { 
     1012            Collection<Command> virtualCmds = new LinkedList<Command>(); 
     1013            virtualCmds.add(new AddCommand(virtualNode)); 
     1014            for (WaySegment virtualWay : virtualWays) { 
     1015                Way w = virtualWay.way; 
     1016                Way wnew = new Way(w); 
     1017                wnew.addNode(virtualWay.lowerIndex + 1, virtualNode); 
     1018                virtualCmds.add(new ChangeCommand(w, wnew)); 
     1019            } 
     1020            virtualCmds.add(new MoveCommand(virtualNode, dx, dy)); 
     1021            String text = trn("Add and move a virtual new node to way", 
     1022                    "Add and move a virtual new node to {0} ways", virtualWays.size(), 
     1023                    virtualWays.size()); 
     1024            Main.main.undoRedo.add(new SequenceCommand(text, virtualCmds)); 
     1025            getCurrentDataSet().setSelected(Collections.singleton((OsmPrimitive) virtualNode)); 
     1026            clear(); 
     1027        } 
     1028 
     1029        private void clear() { 
     1030            virtualWays.clear(); 
     1031            virtualNode = null; 
     1032        } 
     1033 
     1034        private boolean hasVirtualNode() { 
     1035            return virtualNode != null; 
     1036        } 
     1037 
     1038        private boolean hasVirtualWays() { 
     1039            return !virtualWays.isEmpty(); 
     1040        } 
     1041    } 
    9841042} 
Note: See TracChangeset for help on using the changeset viewer.