Index: src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 4763)
+++ src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(working copy)
@@ -77,10 +77,15 @@
     private Color selectedColor;
 
     private Node currentBaseNode;
+    private Node previousNode;
     private EastNorth currentMouseEastNorth;
 
+    private SnapHelper snapHelper = new SnapHelper();
+
     private Shortcut extraShortcut;
     private Shortcut backspaceShortcut;
+    
+    boolean snapOn;
             
     public DrawAction(MapFrame mapFrame) {
         super(tr("Draw"), "node/autonode", tr("Draw nodes"),
@@ -116,60 +121,6 @@
         Main.map.mapView.repaint();
     }
 
-    /**
-     * Takes the data from computeHelperLine to determine which ways/nodes should be highlighted
-     * (if feature enabled). Also sets the target cursor if appropriate.
-     */
-    private void addHighlighting() {
-        removeHighlighting();
-        // if ctrl key is held ("no join"), don't highlight anything
-        if (ctrl) {
-            Main.map.mapView.setNewCursor(cursor, this);
-            return;
-        }
-
-        // This happens when nothing is selected, but we still want to highlight the "target node"
-        if (mouseOnExistingNode == null && getCurrentDataSet().getSelected().size() == 0
-                && mousePos != null) {
-            mouseOnExistingNode = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate);
-        }
-
-        if (mouseOnExistingNode != null) {
-            Main.map.mapView.setNewCursor(cursorJoinNode, this);
-            // We also need this list for the statusbar help text
-            oldHighlights.add(mouseOnExistingNode);
-            if(drawTargetHighlight) {
-                mouseOnExistingNode.setHighlighted(true);
-            }
-            return;
-        }
-
-        // Insert the node into all the nearby way segments
-        if (mouseOnExistingWays.size() == 0) {
-            Main.map.mapView.setNewCursor(cursor, this);
-            return;
-        }
-
-        Main.map.mapView.setNewCursor(cursorJoinWay, this);
-
-        // We also need this list for the statusbar help text
-        oldHighlights.addAll(mouseOnExistingWays);
-        if (!drawTargetHighlight) return;
-        for (Way w : mouseOnExistingWays) {
-            w.setHighlighted(true);
-        }
-    }
-
-    /**
-     * Removes target highlighting from primitives
-     */
-    private void removeHighlighting() {
-        for(OsmPrimitive prim : oldHighlights) {
-            prim.setHighlighted(false);
-        }
-        oldHighlights = new HashSet<OsmPrimitive>();
-    }
-
     @Override public void enterMode() {
         if (!isEnabled())
             return;
@@ -178,6 +129,7 @@
         drawHelperLine = Main.pref.getBoolean("draw.helper-line", true);
         drawTargetHighlight = Main.pref.getBoolean("draw.target-highlight", true);
         wayIsFinished = false;
+        snapHelper.init();
         
         backspaceShortcut = Shortcut.registerShortcut("mapmode:backspace", tr("Backspace in Add mode"), KeyEvent.VK_BACK_SPACE, Shortcut.GROUP_EDIT);
         Main.registerActionShortcut(new BackSpaceAction(), backspaceShortcut);
@@ -224,6 +176,13 @@
     public void eventDispatched(AWTEvent event) {
         if(Main.map == null || Main.map.mapView == null || !Main.map.mapView.isActiveLayerDrawable())
             return;
+        if (event instanceof KeyEvent) {
+                KeyEvent ke = (KeyEvent) event;
+                if (ke.getKeyCode() == KeyEvent.VK_TAB && 
+                    ke.getID()==KeyEvent.KEY_PRESSED) {
+                    snapHelper.nextSnapMode();
+                }
+        } //  toggle angle snapping
         updateKeyModifiers((InputEvent) event);
         computeHelperLine();
         addHighlighting();
@@ -257,7 +216,8 @@
         lastUsedNode = null;
         wayIsFinished = true;
         Main.map.selectSelectTool(true);
-
+        snapHelper.reset();
+    
         // Redraw to remove the helper line stub
         computeHelperLine();
         removeHighlighting();
@@ -308,7 +268,7 @@
             n = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate);
         }
 
-        if (n != null) {
+        if (n != null && !snapHelper.isActive()) {
             // user clicked on node
             if (selection.isEmpty() || wayIsFinished) {
                 // select the clicked node and do nothing else
@@ -327,8 +287,17 @@
                 return;
             }
         } else {
-            // no node found in clicked area
-            n = new Node(Main.map.mapView.getLatLon(e.getX(), e.getY()));
+            EastNorth newEN;
+            if (n!=null) {
+                // project found node to snapping line
+                newEN = snapHelper.getSnapPoint(n.getEastNorth()); 
+            } else { // n==null
+                // no node found in clicked area
+                EastNorth mouseEN = Main.map.mapView.getEastNorth(e.getX(), e.getY());
+                newEN = snapOn ? snapHelper.getSnapPoint(mouseEN) : mouseEN;
+            }
+            snapHelper.resetDirection();
+            n=new Node(newEN); //create node at clicked point
             if (n.getCoor().isOutSideWorld()) {
                 JOptionPane.showMessageDialog(
                         Main.parent,
@@ -343,56 +312,14 @@
             cmds.add(new AddCommand(n));
 
             if (!ctrl) {
-                // Insert the node into all the nearby way segments
-                List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(e.getPoint(), OsmPrimitive.isSelectablePredicate);
-                Map<Way, List<Integer>> insertPoints = new HashMap<Way, List<Integer>>();
-                for (WaySegment ws : wss) {
-                    List<Integer> is;
-                    if (insertPoints.containsKey(ws.way)) {
-                        is = insertPoints.get(ws.way);
-                    } else {
-                        is = new ArrayList<Integer>();
-                        insertPoints.put(ws.way, is);
-                    }
-
-                    is.add(ws.lowerIndex);
-                }
-
-                Set<Pair<Node,Node>> segSet = new HashSet<Pair<Node,Node>>();
-
-                for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) {
-                    Way w = insertPoint.getKey();
-                    List<Integer> is = insertPoint.getValue();
-
-                    Way wnew = new Way(w);
-
-                    pruneSuccsAndReverse(is);
-                    for (int i : is) {
-                        segSet.add(
-                                Pair.sort(new Pair<Node,Node>(w.getNode(i), w.getNode(i+1))));
-                    }
-                    for (int i : is) {
-                        wnew.addNode(i + 1, n);
-                    }
-
-                    // If ALT is pressed, a new way should be created and that new way should get
-                    // selected. This works everytime unless the ways the nodes get inserted into
-                    // are already selected. This is the case when creating a self-overlapping way
-                    // but pressing ALT prevents this. Therefore we must de-select the way manually
-                    // here so /only/ the new way will be selected after this method finishes.
-                    if(alt) {
-                        newSelection.add(insertPoint.getKey());
-                    }
-
-                    cmds.add(new ChangeCommand(insertPoint.getKey(), wnew));
-                    replacedWays.add(insertPoint.getKey());
-                    reuseWays.add(wnew);
-                }
-
-                adjustNode(segSet, n);
-            }
+                    // Insert the node into all the nearby way segments
+                    List<WaySegment> wss = Main.map.mapView.getNearestWaySegments(
+                            Main.map.mapView.getPoint(n), OsmPrimitive.isSelectablePredicate);
+                    insertNodeIntoAllNearbySegments(wss, n, newSelection, cmds, replacedWays, reuseWays);
+                    }    
         }
-
+        // now "n" is newly created or reused node that shoud be added to some way
+        
         // This part decides whether or not a "segment" (i.e. a connection) is made to an
         // existing node.
 
@@ -542,7 +469,56 @@
         removeHighlighting();
         redrawIfRequired();
     }
+    
+    private void insertNodeIntoAllNearbySegments(List<WaySegment> wss, Node n, Collection<OsmPrimitive> newSelection, Collection<Command> cmds, ArrayList<Way> replacedWays, ArrayList<Way> reuseWays) {
+        Map<Way, List<Integer>> insertPoints = new HashMap<Way, List<Integer>>();
+        for (WaySegment ws : wss) {
+            List<Integer> is;
+            if (insertPoints.containsKey(ws.way)) {
+                is = insertPoints.get(ws.way);
+            } else {
+                is = new ArrayList<Integer>();
+                insertPoints.put(ws.way, is);
+            }
 
+            is.add(ws.lowerIndex);
+        }
+
+        Set<Pair<Node,Node>> segSet = new HashSet<Pair<Node,Node>>();
+
+        for (Map.Entry<Way, List<Integer>> insertPoint : insertPoints.entrySet()) {
+            Way w = insertPoint.getKey();
+            List<Integer> is = insertPoint.getValue();
+
+            Way wnew = new Way(w);
+
+            pruneSuccsAndReverse(is);
+            for (int i : is) {
+                segSet.add(
+                        Pair.sort(new Pair<Node,Node>(w.getNode(i), w.getNode(i+1))));
+            }
+            for (int i : is) {
+                wnew.addNode(i + 1, n);
+            }
+
+            // If ALT is pressed, a new way should be created and that new way should get
+            // selected. This works everytime unless the ways the nodes get inserted into
+            // are already selected. This is the case when creating a self-overlapping way
+            // but pressing ALT prevents this. Therefore we must de-select the way manually
+            // here so /only/ the new way will be selected after this method finishes.
+            if(alt) {
+                newSelection.add(insertPoint.getKey());
+            }
+
+            cmds.add(new ChangeCommand(insertPoint.getKey(), wnew));
+            replacedWays.add(insertPoint.getKey());
+            reuseWays.add(wnew);
+        }
+
+        adjustNode(segSet, n);
+    }
+
+
     /**
      * Prevent creation of ways that look like this: <---->
      * This happens if users want to draw a no-exit-sideway from the main way like this:
@@ -641,8 +617,6 @@
 
         Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
 
-        Node selectedNode = null;
-        Way selectedWay = null;
         Node currentMouseNode = null;
         mouseOnExistingNode = null;
         mouseOnExistingWays = new HashSet<Way>();
@@ -674,6 +648,42 @@
             currentMouseEastNorth = mv.getEastNorth(mousePos.x, mousePos.y);
         }
 
+        determineCurrentBaseNodeAndPreviousNode(selection);
+        if (previousNode == null) snapHelper.reset();
+        
+        if (currentBaseNode == null || currentBaseNode == currentMouseNode)
+            return; // Don't create zero length way segments.
+
+        // find out the distance, in metres, between the base point and the mouse cursor
+        LatLon mouseLatLon = mv.getProjection().eastNorth2latlon(currentMouseEastNorth);
+        distance = currentBaseNode.getCoor().greatCircleDistance(mouseLatLon);
+
+        double hdg = Math.toDegrees(currentBaseNode.getEastNorth()
+                .heading(currentMouseEastNorth));
+        if (previousNode != null) {
+            angle = hdg - Math.toDegrees(previousNode.getEastNorth()
+                    .heading(currentBaseNode.getEastNorth()));
+            angle += angle < 0 ? 360 : 0;
+        }
+        
+        if (snapOn) snapHelper.checkAngleSnapping(currentMouseEastNorth,angle);
+        
+        Main.map.statusLine.setAngle(angle);
+        Main.map.statusLine.setHeading(hdg);
+        Main.map.statusLine.setDist(distance);
+        // Now done in redrawIfRequired()
+        //updateStatusLine();
+    }
+    
+    
+    /** 
+     * Helper function that sets fields currentBaseNode and previousNode 
+     * @param selection 
+     * uses also lastUsedNode field
+     */
+    private void determineCurrentBaseNodeAndPreviousNode(Collection<OsmPrimitive>  selection) {
+        Node selectedNode = null;
+        Way selectedWay = null;
         for (OsmPrimitive p : selection) {
             if (p instanceof Node) {
                 if (selectedNode != null) return;
@@ -683,10 +693,11 @@
                 selectedWay = (Way) p;
             }
         }
+        // we are here, if not more than 1 way or node is selected,
 
         // the node from which we make a connection
         currentBaseNode = null;
-        Node previousNode = null;
+        previousNode = null;
 
         if (selectedNode == null) {
             if (selectedWay == null)
@@ -700,33 +711,19 @@
         } else if (selectedWay == null) {
             currentBaseNode = selectedNode;
         } else if (!selectedWay.isDeleted()) { // fix #7118
-            if (selectedNode == selectedWay.getNode(0) || selectedNode == selectedWay.getNode(selectedWay.getNodesCount()-1)) {
+            if (selectedNode == selectedWay.getNode(0)){
                 currentBaseNode = selectedNode;
+                if (selectedWay.getNodesCount()>1) previousNode = selectedWay.getNode(1);
             }
+            if (selectedNode == selectedWay.lastNode()) {
+                currentBaseNode = selectedNode;
+                if (selectedWay.getNodesCount()>1) 
+                    previousNode = selectedWay.getNode(selectedWay.getNodesCount()-2);
+            }
         }
+    }
 
-        if (currentBaseNode == null || currentBaseNode == currentMouseNode)
-            return; // Don't create zero length way segments.
 
-        // find out the distance, in metres, between the base point and the mouse cursor
-        LatLon mouseLatLon = mv.getProjection().eastNorth2latlon(currentMouseEastNorth);
-        distance = currentBaseNode.getCoor().greatCircleDistance(mouseLatLon);
-
-        double hdg = Math.toDegrees(currentBaseNode.getEastNorth()
-                .heading(currentMouseEastNorth));
-        if (previousNode != null) {
-            angle = hdg - Math.toDegrees(previousNode.getEastNorth()
-                    .heading(currentBaseNode.getEastNorth()));
-            angle += angle < 0 ? 360 : 0;
-        }
-
-        Main.map.statusLine.setAngle(angle);
-        Main.map.statusLine.setHeading(hdg);
-        Main.map.statusLine.setDist(distance);
-        // Now done in redrawIfRequired()
-        //updateStatusLine();
-    }
-
     /**
      * Repaint on mouse exit so that the helper line goes away.
      */
@@ -734,6 +731,7 @@
         if(!Main.map.mapView.isActiveLayerDrawable())
             return;
         mousePos = e.getPoint();
+        snapHelper.reset();
         Main.map.mapView.repaint();
     }
 
@@ -741,7 +739,7 @@
      * @return If the node is the end of exactly one way, return this.
      *  <code>null</code> otherwise.
      */
-    public Way getWayForNode(Node n) {
+    public static Way getWayForNode(Node n) {
         Way way = null;
         for (Way w : Utils.filteredCollection(n.getReferrers(), Way.class)) {
             if (!w.isUsable() || w.getNodesCount() < 1) {
@@ -852,10 +850,61 @@
     static double det(double a, double b, double c, double d) {
         return a * d - b * c;
     }
+/**
+     * Takes the data from computeHelperLine to determine which ways/nodes should be highlighted
+     * (if feature enabled). Also sets the target cursor if appropriate.
+     */
+    private void addHighlighting() {
+        removeHighlighting();
+        // if ctrl key is held ("no join"), don't highlight anything
+        if (ctrl) {
+            Main.map.mapView.setNewCursor(cursor, this);
+            return;
+        }
 
+        // This happens when nothing is selected, but we still want to highlight the "target node"
+        if (mouseOnExistingNode == null && getCurrentDataSet().getSelected().size() == 0
+                && mousePos != null) {
+            mouseOnExistingNode = Main.map.mapView.getNearestNode(mousePos, OsmPrimitive.isSelectablePredicate);
+        }
+
+        if (mouseOnExistingNode != null) {
+            Main.map.mapView.setNewCursor(cursorJoinNode, this);
+            // We also need this list for the statusbar help text
+            oldHighlights.add(mouseOnExistingNode);
+            if(drawTargetHighlight) {
+                mouseOnExistingNode.setHighlighted(true);
+        }
+            return;
+        }
+
+        // Insert the node into all the nearby way segments
+        if (mouseOnExistingWays.size() == 0) {
+            Main.map.mapView.setNewCursor(cursor, this);
+            return;
+    }
+
+        Main.map.mapView.setNewCursor(cursorJoinWay, this);
+
+        // We also need this list for the statusbar help text
+        oldHighlights.addAll(mouseOnExistingWays);
+        if (!drawTargetHighlight) return;
+        for (Way w : mouseOnExistingWays) {
+            w.setHighlighted(true);
+        }
+    }
+
+    /**
+     * Removes target highlighting from primitives
+     */
+    private void removeHighlighting() {
+        for(OsmPrimitive prim : oldHighlights) {
+            prim.setHighlighted(false);
+        }
+        oldHighlights = new HashSet<OsmPrimitive>();
+    }
+    
     public void paint(Graphics2D g, MapView mv, Bounds box) {
-        if (!drawHelperLine || wayIsFinished || shift) return;
-
         // sanity checks
         if (Main.map.mapView == null) return;
         if (mousePos == null) return;
@@ -865,8 +914,11 @@
 
         // don't draw line if mouse is outside window
         if (!Main.map.mapView.getBounds().contains(mousePos)) return;
-
+        
         Graphics2D g2 = g;
+        if (snapOn) snapHelper.draw(g2,mv);
+        if (!drawHelperLine || wayIsFinished || shift) return;
+        
         g2.setColor(selectedColor);
         g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
         GeneralPath b = new GeneralPath();
@@ -975,7 +1027,7 @@
         super.destroy();
         Main.unregisterActionShortcut(extraShortcut);
     }
-    
+
     public static class BackSpaceAction extends AbstractAction {
 
         @Override
@@ -1001,4 +1053,118 @@
     }
     }
 
+    private class SnapHelper {
+        private boolean active;
+        private boolean fixed;
+        EastNorth dir2;
+        EastNorth projected;
+        String labelText;
+        double lastAngle;
+        
+        double snapAngleStep; 
+        double snapAngleTolerance; 
+        
+        double pe,pn; // (pe,pn) - direction of snapping line
+        double e0,n0; // (e0,n0) - origin of snapping line
+        
+        public SnapHelper() { }
+        
+        private  void init() {
+            reset(); fixed=false; snapOn=false;
+            snapAngleStep = Main.pref.getDouble("draw.anglesnap.step", 90.0);
+            snapAngleTolerance = Main.pref.getDouble("draw.anglesnap.tol", 20.0);
+        }
+        
+        private  void reset() {
+            active=false;
+            dir2=null; projected=null;
+            labelText=null;
+        }
+
+        private void draw(Graphics2D g2, MapView mv) {
+            if (!active) return;
+            g2.setColor(Color.ORANGE);
+            g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+            GeneralPath b = new GeneralPath();
+            Point p1=mv.getPoint(currentBaseNode);
+            Point p2=mv.getPoint(dir2);
+            Point p3=mv.getPoint(projected);
+            
+            b.moveTo(p1.x,p1.y); 
+            b.lineTo(p2.x,p2.y);
+            g2.draw(b);        
+            g2.drawString(labelText, p3.x-5, p3.y+20);
+            g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+            g2.drawOval(p3.x-5, p3.y-5, 10, 10); // projected point
+        }
+
+        /* If mouse position is close to line at 15-30-45-... angle, remembers this direction
+         */
+        private void checkAngleSnapping(EastNorth currentEN, double angle) {
+            if (previousNode==null) return;
+            if (fixed) angle = lastAngle; // if direction is fixed
+            double nearestAngle = snapAngleStep*Math.round(angle/snapAngleStep);
+            // 95->90, 50->90, 40->0, 280->270, 340->360
+            
+            if (Math.abs(nearestAngle-180)>1e-3 && Math.abs(nearestAngle-angle)<snapAngleTolerance) {
+                if (Math.abs(nearestAngle-360)<1e-3) nearestAngle=0;
+                active=true;
+                if (fixed) labelText = String.format("%d FIX", (int) nearestAngle);
+                      else labelText = String.format("%d", (int) nearestAngle);
+                EastNorth prev = previousNode.getEastNorth();
+                EastNorth p0 = currentBaseNode.getEastNorth();
+                
+                double de,dn,l, phi;
+                e0=p0.east(); n0=p0.north();
+                de = e0-prev.east();
+                dn = n0-prev.north();
+                l=Math.hypot(de, dn);
+                de/=l; dn/=l;
+                
+                phi=nearestAngle*Math.PI/180;
+                // (pe,pn) - direction of snapping line
+                pe = de*Math.cos(phi) + dn*Math.sin(phi);  
+                pn = -de*Math.sin(phi) + dn*Math.cos(phi);
+                double scale = 20*Main.map.mapView.getDist100Pixel();
+                dir2 = new EastNorth( e0+scale*pe, n0+scale*pn);
+                lastAngle = nearestAngle;
+                getSnapPoint(currentEN); 
+           } else {
+                reset();
+           }
+        }
+        
+        private EastNorth getSnapPoint(EastNorth p) {
+            if (!active) return p;
+            double de=p.east()-e0;
+            double dn=p.north()-n0;
+            double l = de*pe+dn*pn;
+            if (l<1e-5) {active=false; return p; } //  do not go backward!
+            return projected = new EastNorth(e0+l*pe, n0+l*pn);
+        }
+
+        private void fixDirection() {
+            if (active) {
+                fixed=true;
+            }
+        }
+
+        private void resetDirection() {
+            lastAngle=0;
+        }
+        
+        private void nextSnapMode() {
+            if (snapOn) {
+                if (fixed) { snapOn=false; fixed=false; }
+                else fixDirection();
+            } else {
+                snapOn=true;
+                fixed=false;
+            }
+        }
+
+        private boolean isActive() {
+            return active;
+        }
+    }
 }
