Changeset 5370 in josm for trunk/src/org/openstreetmap


Ignore:
Timestamp:
2012-07-28T09:59:43+02:00 (12 years 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.