Index: src/org/openstreetmap/josm/actions/mapmode/DrawAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/DrawAction.java	(revision 4707)
+++ 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"),
@@ -95,6 +100,54 @@
         cursorJoinWay = ImageProvider.getCursor("crosshair", "joinway");
     }
 
+    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);
+    }
+
     /**
      * Checks if a map redraw is required and does so if needed. Also updates the status bar
      */
@@ -116,60 +169,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;
@@ -224,6 +223,11 @@
     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) snapOn=!snapOn;
+        } //  toggle angle snapping
         updateKeyModifiers((InputEvent) event);
         computeHelperLine();
         addHighlighting();
@@ -257,7 +261,8 @@
         lastUsedNode = null;
         wayIsFinished = true;
         Main.map.selectSelectTool(true);
-
+        snapHelper.reset();
+    
         // Redraw to remove the helper line stub
         computeHelperLine();
         removeHighlighting();
@@ -293,6 +298,10 @@
         // if that can ever happen but better be safe.
         updateKeyModifiers(e);
         mousePos = e.getPoint();
+        EastNorth mouseEN = Main.map.mapView.getEastNorth(e.getX(), e.getY());;
+        if (snapOn) {
+            mouseEN = snapHelper.getSnapPoint(mouseEN);
+        }
 
         DataSet ds = getCurrentDataSet();
         Collection<OsmPrimitive> selection = new ArrayList<OsmPrimitive>(ds.getSelected());
@@ -328,7 +337,7 @@
             }
         } else {
             // no node found in clicked area
-            n = new Node(Main.map.mapView.getLatLon(e.getX(), e.getY()));
+            n=new Node(mouseEN);
             if (n.getCoor().isOutSideWorld()) {
                 JOptionPane.showMessageDialog(
                         Main.parent,
@@ -345,54 +354,10 @@
             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);
+                insertNodeIntoAllNearbySegments(wss, n, newSelection, cmds, replacedWays, reuseWays);
                     }
-
-                    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);
-            }
-        }
-
         // This part decides whether or not a "segment" (i.e. a connection) is made to an
         // existing node.
 
@@ -641,8 +606,6 @@
 
         Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
 
-        Node selectedNode = null;
-        Way selectedWay = null;
         Node currentMouseNode = null;
         mouseOnExistingNode = null;
         mouseOnExistingWays = new HashSet<Way>();
@@ -674,6 +637,35 @@
             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();
+    }
+    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 +675,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)
@@ -704,36 +697,70 @@
                 currentBaseNode = selectedNode;
             }
         }
+    }
 
-        if (currentBaseNode == null || currentBaseNode == currentMouseNode)
-            return; // Don't create zero length way segments.
+/**
+     * 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;
+        }
 
-        // 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);
+        // 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);
+        }
 
-        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 (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;
+        }
 
-        Main.map.statusLine.setAngle(angle);
-        Main.map.statusLine.setHeading(hdg);
-        Main.map.statusLine.setDist(distance);
-        // Now done in redrawIfRequired()
-        //updateStatusLine();
+        // 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>();
+    }
+    
+    /**
      * Repaint on mouse exit so that the helper line goes away.
      */
     @Override public void mouseExited(MouseEvent e) {
         if(!Main.map.mapView.isActiveLayerDrawable())
             return;
         mousePos = e.getPoint();
+        snapHelper.reset();
         Main.map.mapView.repaint();
     }
 
@@ -741,7 +768,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) {
@@ -854,8 +881,7 @@
     }
 
     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 +891,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;
 
-        Graphics2D g2 = g;
         g2.setColor(selectedColor);
         g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
         GeneralPath b = new GeneralPath();
@@ -1001,4 +1030,82 @@
     }
     }
 
+    private class SnapHelper {
+        private boolean active;
+        EastNorth dir2;
+        EastNorth projected;
+        String labelText;
+        
+        
+        double pe,pn; // (pe,pn) - direction of snapping line
+        double e0,n0; // (e0,n0) - origin of snapping line
+        
+        
+         
+        public SnapHelper() { }
+        
+        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 currrentEN, double angle) {
+            if (previousNode==null) return;
+            
+            double nearestAngle = 15*Math.round(angle/15);
+            // 95->90, 50->90, 40->0, 280->270, 340->360
+            if (nearestAngle!=180 && Math.abs(nearestAngle-angle)<7) {
+                active=true;
+                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);
+                getSnapPoint(currrentEN);
+           } 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;
+            return projected = new EastNorth(e0+l*pe, n0+l*pn);
+        }
+    }
+
 }
