Ticket #383: Update DrawAction.patch

File Update DrawAction.patch, 19.9 KB (added by xeen, 17 years ago)
  • src/org/openstreetmap/josm/actions/mapmode/DrawAction.java

     
    131131     * (if feature enabled). Also sets the target cursor if appropriate.
    132132     */
    133133    private void addHighlighting() {
     134        removeHighlighting();
    134135        // if ctrl key is held ("no join"), don't highlight anything
    135136        if (ctrl) {
    136             removeHighlighting();
    137137            resetCursor();
    138138            return;
    139139        }
    140140
     141        // This happens when nothing is selected
     142        if(mouseOnExistingNode == null && Main.ds.getSelected().size() == 0)
     143            mouseOnExistingNode = Main.map.mapView.getNearestNode(mousePos);
     144
    141145        if (mouseOnExistingNode != null) {
    142146            setJoinCursor(false);
    143             // Clean old highlights
    144             removeHighlighting();
    145             if(drawTargetHighlight) {
    146                 oldHighlights.add(mouseOnExistingNode);
     147            // We also need this list for the statusbar help text
     148            oldHighlights.add(mouseOnExistingNode);
     149            if(drawTargetHighlight)
    147150                mouseOnExistingNode.highlighted = true;
    148             }
    149151            return;
    150152        }
    151153
    152154        // Insert the node into all the nearby way segments
    153155        if(mouseOnExistingWays.size() == 0) {
    154             removeHighlighting();
    155156            resetCursor();
    156157            return;
    157158        }
    158159
    159160        setJoinCursor(true);
    160         // Clean old highlights
    161         removeHighlighting();
    162161
     162        // We also need this list for the statusbar help text
     163        oldHighlights.addAll(mouseOnExistingWays);
    163164        if(!drawTargetHighlight) return;
    164         oldHighlights.addAll(mouseOnExistingWays);
    165165        for(Way w : mouseOnExistingWays) {
    166166            w.highlighted = true;
    167167        }
     
    174174        for(OsmPrimitive prim : oldHighlights) {
    175175            prim.highlighted = false;
    176176        }
     177        oldHighlights = new HashSet<OsmPrimitive>();
    177178    }
    178179
    179180    @Override public void enterMode() {
     
    214215     * redraw to (possibly) get rid of helper line if selection changes.
    215216     */
    216217    public void eventDispatched(AWTEvent event) {
    217         if(Main.map == null ||Main.map.mapView == null || !Main.map.mapView.isDrawableLayer())
     218        if(Main.map == null || Main.map.mapView == null || !Main.map.mapView.isDrawableLayer())
    218219            return;
    219         InputEvent e = (InputEvent) event;
    220         ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    221         alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
    222         shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     220        updateKeyModifiers((InputEvent) event);
     221        addHighlighting();
    223222        computeHelperLine();
    224223    }
    225224    /**
     
    237236    }
    238237
    239238    /**
     239     * This function should be called when the user wishes to finish his current draw action.
     240     * If Potlatch Style is enabled, it will switch to select tool, otherwise simply disable
     241     * the helper line until the user chooses to draw something else.
     242     */
     243    private void finishDrawing() {
     244        lastUsedNode = null;
     245        wayIsFinished = true;
     246        Main.map.selectSelectTool(true);
     247
     248        // Redraw to remove the helper line stub
     249        removeHighlighting();
     250        computeHelperLine();
     251        Main.map.mapView.repaint();
     252    }
     253
     254    /**
    240255     * If user clicked with the left button, add a node at the current mouse
    241256     * position.
    242257     *
     
    247262            return;
    248263        if(!Main.map.mapView.isDrawableLayer())
    249264            return;
    250        
     265
    251266        if(e.getClickCount() > 1 && mousePos != null && mousePos.equals(oldMousePos)) {
    252267            // A double click equals "user clicked last node again, finish way"
    253268            // Change draw tool only if mouse position is nearly the same, as
    254269            // otherwise fast clicks will count as a double click
    255             lastUsedNode = null;
    256             wayIsFinished = true;
    257 
    258             Main.map.selectSelectTool(true);
     270            finishDrawing();
    259271            return;
    260272        }
    261273        oldMousePos = mousePos;
    262        
     274
    263275        // we copy ctrl/alt/shift from the event just in case our global
    264276        // AWTEvent didn't make it through the security manager. Unclear
    265277        // if that can ever happen but better be safe.
    266         ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    267         alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
    268         shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
     278        updateKeyModifiers(e);
    269279        mousePos = e.getPoint();
    270280
    271281        Collection<OsmPrimitive> selection = Main.ds.getSelected();
     
    276286        boolean newNode = false;
    277287        Node n = null;
    278288
    279         if (!ctrl)
     289        if (!ctrl && !shift)
    280290            n = Main.map.mapView.getNearestNode(mousePos);
    281291
    282292        if (n != null) {
    283293            // user clicked on node
    284             if (shift || selection.isEmpty()) {
     294            if (selection.isEmpty()) {
    285295                // select the clicked node and do nothing else
    286296                // (this is just a convenience option so that people don't
    287297                // have to switch modes)
     
    329339                        Pair.sort(new Pair<Node,Node>(w.nodes.get(i), w.nodes.get(i+1))));
    330340                    for (int i : is) wnew.addNode(i + 1, n);
    331341
     342                    // If ALT is pressed, a new way should be created and that new way should get
     343                    // selected. This works everytime unless the ways the nodes get inserted into
     344                    // are already selected. This is the case when creating a self-overlapping way
     345                    // but pressing ALT prevents this. Therefore we must de-select the way manually
     346                    // here so /only/ the new way will be selected after this method finishes.
     347                    if(alt) wnew.selected = false;
     348
    332349                    cmds.add(new ChangeCommand(insertPoint.getKey(), wnew));
    333350                    replacedWays.add(insertPoint.getKey());
    334351                    reuseWays.add(wnew);
     
    350367        boolean extendedWay = false;
    351368        boolean wayIsFinishedTemp = wayIsFinished;
    352369        wayIsFinished = false;
    353         if (!shift && selection.size() > 0 && !wayIsFinishedTemp) {
     370        Way way = null;
     371        if (selection.size() > 0 && !wayIsFinishedTemp) {
    354372            Node selectedNode = null;
    355373            Way selectedWay = null;
    356374
     
    372390                }
    373391            }
    374392
    375             // No nodes or ways have been selected, try again with no selection
    376             // This occurs when a relation has been selected
    377             if(selectedNode == null && selectedWay == null) {
     393            /*
     394             * the node from which we make a connection
     395             */
     396            Node n0 = findNodeToContinueFrom(selectedNode, selectedWay);
     397            // We have a selection but it isn't suitable. Try again.
     398            if(n0 == null) {
    378399                tryAgain(e);
    379400                return;
    380401            }
    381402
    382             // the node from which we make a connection
    383             Node n0 = null;
     403            if(isSelfContainedWay(selectedWay, n0, n))
     404                return;
    384405
    385             if (selectedNode == null) {
    386                 if (selectedWay.isFirstLastNode(lastUsedNode)) {
    387                     n0 = lastUsedNode;
    388                 } else {
    389                     // We have a way selected, but no suitable node to continue from. Start anew.
    390                     tryAgain(e);
    391                     return;
    392                 }
    393             } else if (selectedWay == null) {
    394                 n0 = selectedNode;
    395             } else {
    396                 if (selectedWay.isFirstLastNode(selectedNode)) {
    397                     n0 = selectedNode;
    398                 } else {
    399                     // We have a way and node selected, but it's not at the start/end of the way. Start anew.
    400                     tryAgain(e);
    401                     return;
    402                 }
    403             }
    404 
    405             // Prevent creation of ways that look like this: <---->
    406             // This happens if users want to draw a no-exit-sideway from the main way like this:
    407             // ^
    408             // |<---->
    409             // |
    410             // The solution isn't ideal because the main way will end in the side way, which is bad for
    411             // navigation software ("drive straight on") but at least easier to fix. Maybe users will fix
    412             // it on their own, too. At least it's better than producing an error.
    413             if(selectedWay != null && selectedWay.nodes != null) {
    414                 int posn0 = selectedWay.nodes.indexOf(n0);
    415                 if( posn0 != -1 && // n0 is part of way
    416                     (posn0 >= 1                          && n.equals(selectedWay.nodes.get(posn0-1))) || // previous node
    417                     (posn0 < selectedWay.nodes.size()-1) && n.equals(selectedWay.nodes.get(posn0+1))) {  // next node
    418                     Main.ds.setSelected(n);
    419                     lastUsedNode = n;
    420                     return;
    421                 }
    422             }
    423 
    424406            // User clicked last node again, finish way
    425407            if(n0 == n) {
    426                 lastUsedNode = null;
    427                 wayIsFinished = true;
    428                 Main.map.selectSelectTool(true);
     408                finishDrawing();
    429409                return;
    430410            }
    431411
    432412            // Ok we know now that we'll insert a line segment, but will it connect to an
    433413            // existing way or make a new way of its own? The "alt" modifier means that the
    434414            // user wants a new way.
    435             Way way = alt ? null : (selectedWay != null) ? selectedWay : getWayForNode(n0);
     415            way = alt ? null : (selectedWay != null) ? selectedWay : getWayForNode(n0);
    436416
    437417            // Don't allow creation of self-overlapping ways
    438418            if(way != null) {
     
    459439
    460440            // Connected to a node that's already in the way
    461441            if(way.nodes.contains(n)) {
    462                 //System.out.println("Stop drawing, node is part of current way");
    463442                wayIsFinished = true;
    464443                selection.clear();
    465444            }
     
    497476
    498477        Main.main.undoRedo.add(c);
    499478        if(!wayIsFinished) lastUsedNode = n;
     479
    500480        computeHelperLine();
    501481        removeHighlighting();
    502482        Main.map.mapView.repaint();
    503483    }
    504484
     485    /**
     486     * Prevent creation of ways that look like this: <---->
     487     * This happens if users want to draw a no-exit-sideway from the main way like this:
     488     * ^
     489     * |<---->
     490     * |
     491     * The solution isn't ideal because the main way will end in the side way, which is bad for
     492     * navigation software ("drive straight on") but at least easier to fix. Maybe users will fix
     493     * it on their own, too. At least it's better than producing an error.
     494     *
     495     * @param Way the way to check
     496     * @param Node the current node (i.e. the one the connection will be made from)
     497     * @param Node the target node (i.e. the one the connection will be made to)
     498     * @return Boolean True if this would create a selfcontaining way, false otherwise.
     499     */
     500    private boolean isSelfContainedWay(Way selectedWay, Node currentNode, Node targetNode) {
     501        if(selectedWay != null && selectedWay.nodes != null) {
     502            int posn0 = selectedWay.nodes.indexOf(currentNode);
     503            if( posn0 != -1 && // n0 is part of way
     504                (posn0 >= 1                          && targetNode.equals(selectedWay.nodes.get(posn0-1))) || // previous node
     505                (posn0 < selectedWay.nodes.size()-1) && targetNode.equals(selectedWay.nodes.get(posn0+1))) {  // next node
     506                Main.ds.setSelected(targetNode);
     507                lastUsedNode = targetNode;
     508                return true;
     509            }
     510        }
     511
     512        return false;
     513    }
     514
     515    /**
     516     * Finds a node to continue drawing from. Decision is based upon given node and way.
     517     * @param selectedNode Currently selected node, may be null
     518     * @param selectedWay Currently selected way, may be null
     519     * @return Node if a suitable node is found, null otherwise
     520     */
     521    private Node findNodeToContinueFrom(Node selectedNode, Way selectedWay) {
     522        // No nodes or ways have been selected, this occurs when a relation
     523        // has been selected or the selection is empty
     524        if(selectedNode == null && selectedWay == null)
     525            return null;
     526
     527        if (selectedNode == null) {
     528            if (selectedWay.isFirstLastNode(lastUsedNode))
     529                return lastUsedNode;
     530
     531            // We have a way selected, but no suitable node to continue from. Start anew.
     532            return null;
     533        }
     534
     535        if (selectedWay == null)
     536            return selectedNode;
     537
     538        if (selectedWay.isFirstLastNode(selectedNode))
     539            return selectedNode;
     540
     541        // We have a way and node selected, but it's not at the start/end of the way. Start anew.
     542        return null;
     543    }
     544
    505545    @Override public void mouseMoved(MouseEvent e) {
    506546        if(!Main.map.mapView.isDrawableLayer())
    507547            return;
     
    509549        // we copy ctrl/alt/shift from the event just in case our global
    510550        // AWTEvent didn't make it through the security manager. Unclear
    511551        // if that can ever happen but better be safe.
     552        updateKeyModifiers(e);
     553        mousePos = e.getPoint();
    512554
     555        addHighlighting();
     556        computeHelperLine();
     557    }
     558
     559    private void updateKeyModifiers(InputEvent e) {
    513560        ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
    514561        alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
    515562        shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
    516         mousePos = e.getPoint();
     563    }
    517564
    518         addHighlighting();
    519         computeHelperLine();
     565    private void updateKeyModifiers(MouseEvent e) {
     566        ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
     567        alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
     568        shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
    520569    }
    521570
    522571    /**
     
    531580            currentBaseNode = null;
    532581            return;
    533582        }
    534        
     583
    535584        double distance = -1;
    536585        double angle = -1;
    537586
     
    547596        Main.map.statusLine.setHeading(-1);
    548597        Main.map.statusLine.setDist(-1);
    549598
    550         if (!ctrl && mousePos != null) {
     599        if (!ctrl && !shift && mousePos != null) {
    551600            currentMouseNode = Main.map.mapView.getNearestNode(mousePos);
    552601        }
    553602
     
    585634
    586635        if (selectedNode == null) {
    587636            if (selectedWay == null) return;
    588             if (lastUsedNode == selectedWay.nodes.get(0) || lastUsedNode == selectedWay.nodes.get(selectedWay.nodes.size()-1)) {
     637            if (selectedWay.isFirstLastNode(lastUsedNode)) {
    589638                currentBaseNode = lastUsedNode;
    590639                if (lastUsedNode == selectedWay.nodes.get(selectedWay.nodes.size()-1) && selectedWay.nodes.size() > 1) {
    591640                    previousNode = selectedWay.nodes.get(selectedWay.nodes.size()-2);
     
    600649        }
    601650
    602651        if (currentBaseNode == null || currentBaseNode == currentMouseNode) {
     652            updateStatusLine();
    603653            return; // Don't create zero length way segments.
    604654        }
    605655
     
    616666        Main.map.statusLine.setDist(distance);
    617667        updateStatusLine();
    618668
    619         if ((!drawHelperLine || wayIsFinished) && !drawTargetHighlight) return;
     669        if (!drawHelperLine || wayIsFinished) return;
    620670        Main.map.mapView.repaint();
    621671    }
    622672
     
    738788    }
    739789
    740790    // helper for adjustNode
    741     static double det(double a, double b, double c, double d)
    742     {
     791    static double det(double a, double b, double c, double d) {
    743792        return a * d - b * c;
    744793    }
    745794
     
    750799        if (Main.map.mapView == null) return;
    751800        if (mousePos == null) return;
    752801
    753         // if shift key is held ("no auto-connect"), don't draw a line
    754         if (shift) return;
    755 
    756802        // don't draw line if we don't know where from or where to
    757803        if (currentBaseNode == null || currentMouseEastNorth == null) return;
    758804
     
    781827    }
    782828
    783829    @Override public String getModeHelpText() {
    784         String rv;
     830        String rv = "";
     831        /*
     832         *  No modifiers: all (Connect, Node Re-Use, Auto-Weld)
     833         *  CTRL: disables node re-use, auto-weld
     834         *  Shift: disables node re-use
     835         *  ALT: disables connect
     836         */
    785837
    786         if (currentBaseNode != null && !shift) {
    787             if (mouseOnExistingNode != null) {
    788                 if (alt && /* FIXME: way exists */true)
    789                     rv = tr("Click to create a new way to the existing node.");
     838        /*
     839         * Status line text generation is split into two parts to keep it maintainable.
     840         * First part looks at what will happen to the new node inserted on click and
     841         * the second part will look if a connection is made or not.
     842         *
     843         * Note that this help text is not absolutely accurate as it doesn't catch any special
     844         * cases (e.g. when preventing <---> ways). The only special that it catches is when
     845         * a way is about to be finished.
     846         *
     847         * First check what happens to the new node.
     848         */
     849
     850        // oldHighlights stores the current highlights. If this
     851        // list is empty we can assume that we won't do any joins
     852        if(ctrl || oldHighlights.isEmpty())
     853            rv = tr("Create new node.");
     854        else if(shift) {
     855            // We already know oldHighlights is not empty, but shift is pressed.
     856            // We can assume the new node will be joined into an existing way
     857            rv = tr("Insert new node into {0} way(s).", oldHighlights.size());
     858        } else {
     859            // oldHighlights may store a node or way, check if it's a node
     860            for(OsmPrimitive x : oldHighlights) {
     861                if(x instanceof Node)
     862                    rv = tr("Select node under cursor.");
    790863                else
    791                     rv =tr("Click to make a connection to the existing node.");
    792             } else {
    793                 if (alt && /* FIXME: way exists */true)
    794                     rv = tr("Click to insert a node and create a new way.");
    795                 else
    796                     rv = tr("Click to insert a new node and make a connection.");
     864                    rv = tr("Insert new node into {0} way(s).", oldHighlights.size());
     865                break;
    797866            }
    798867        }
    799         else {
    800             rv = tr("Click to insert a new node.");
     868
     869        /*
     870         * Check whether a connection will be made
     871         */
     872        if (currentBaseNode != null) {
     873            if(alt)
     874                rv += " " + tr("Start new way from last node.");
     875            else
     876                rv += " " + tr("Continue way from last node.");
    801877        }
    802878
    803         //rv.append(tr("Click to add a new node. Ctrl: no node re-use/auto-insert. Shift: no auto-connect. Alt: new way"));
    804         //rv.append(tr("Click to add a new node. Ctrl: no node re-use/auto-insert. Shift: no auto-connect. Alt: new way"));
    805         return rv.toString();
     879        /*
     880         * Handle special case: Highlighted node == selected node => finish drawing
     881         */
     882        Node n = mouseOnExistingNode;
     883        if(n != null && Main.ds.getSelectedNodes().contains(n)) {
     884            rv = tr("Finish drawing.");
     885        }
     886
     887        /*
     888         * Handle special case: Self-Overlapping or closing way
     889         */
     890        if(Main.ds.getSelectedWays().size() > 0 && !wayIsFinished && !alt) {
     891            Way w = (Way) Main.ds.getSelectedWays().iterator().next();
     892            for(Node m : w.nodes) {
     893                if(m.equals(mouseOnExistingNode) || mouseOnExistingWays.contains(w)) {
     894                    rv += " " + tr("Finish drawing.");
     895                    break;
     896                }
     897            }
     898        }
     899
     900        return rv;
    806901    }
    807902
    808903    @Override public boolean layerIsSupported(Layer l) {