Index: /trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java	(revision 3556)
+++ /trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java	(revision 3557)
@@ -18,6 +18,8 @@
 import java.awt.geom.NoninvertibleTransformException;
 import java.awt.geom.Point2D;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedList;
+import java.util.List;
 
 import org.openstreetmap.josm.Main;
@@ -48,5 +50,11 @@
 
     enum Mode { extrude, translate, select }
+
     private Mode mode = Mode.select;
+
+    /**
+     * If true, when extruding create new node even if segments prallel.
+     */
+    private boolean alwaysCreateNodes = false;
     private long mouseDownTime = 0;
     private WaySegment selectedSegment = null;
@@ -54,4 +62,14 @@
 
     /**
+     * Possible directions to move to.
+     */
+    private List<EastNorth> possibleMoveDirections;
+
+    /**
+     * The direction that is currently active.
+     */
+    private EastNorth activeMoveDirection;
+
+    /**
      * The old cursor before the user pressed the mouse button.
      */
@@ -76,8 +94,10 @@
     private EastNorth newN1en;
     private EastNorth newN2en;
-    /**
-     * This is to work around some deficiencies in MoveCommand when translating
-     */
-    private EastNorth lastTranslatedN1en;
+
+    /**
+     * the command that performed last move.
+     */
+    private MoveCommand moveCommand;
+
     /**
      * Create a new SelectAction
@@ -94,24 +114,15 @@
     }
 
-    private static Cursor getCursor(String name, String mod, int def) {
-        try {
-            return ImageProvider.getCursor(name, mod);
-        } catch (Exception e) {
-        }
-        return Cursor.getPredefinedCursor(def);
-    }
-
-    private void setCursor(Cursor c) {
-        if (oldCursor == null) {
-            oldCursor = Main.map.mapView.getCursor();
-            Main.map.mapView.setCursor(c);
-        }
-    }
-
-    private void restoreCursor() {
-        if (oldCursor != null) {
-            Main.map.mapView.setCursor(oldCursor);
-            oldCursor = null;
-        }
+    @Override public String getModeHelpText() {
+        if (mode == Mode.translate)
+            return tr("Move a segment along its normal, then release the mouse button.");
+        else if (mode == Mode.extrude)
+            return tr("Draw a rectangle of the desired size, then release the mouse button.");
+        else
+            return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal.");
+    }
+
+    @Override public boolean layerIsSupported(Layer l) {
+        return l instanceof OsmDataLayer;
     }
 
@@ -123,8 +134,81 @@
 
     @Override public void exitMode() {
-        super.exitMode();
         Main.map.mapView.removeMouseListener(this);
         Main.map.mapView.removeMouseMotionListener(this);
         Main.map.mapView.removeTemporaryLayer(this);
+        super.exitMode();
+    }
+
+    /**
+     * If the left mouse button is pressed over a segment, switch
+     * to either extrude or translate mode depending on whether ctrl is held.
+     */
+    @Override public void mousePressed(MouseEvent e) {
+        if(!Main.map.mapView.isActiveLayerVisible())
+            return;
+        if (!(Boolean)this.getValue("active"))
+            return;
+        if (e.getButton() != MouseEvent.BUTTON1)
+            return;
+
+        selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
+
+        if (selectedSegment == null) {
+            // If nothing gets caught, stay in select mode
+        } else {
+            // Otherwise switch to another mode
+
+            if ((e.getModifiers() & ActionEvent.CTRL_MASK) != 0) {
+                mode = Mode.translate;
+            } else {
+                mode = Mode.extrude;
+                getCurrentDataSet().setSelected(selectedSegment.way);
+                alwaysCreateNodes = ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0);
+            }
+
+            // remember initial positions for segment nodes.
+            initialN1en = selectedSegment.getFirstNode().getEastNorth();
+            initialN2en = selectedSegment.getSecondNode().getEastNorth();
+
+            //gather possible move directions - perpendicular to the selected segment and parallel to neighbor segments
+            possibleMoveDirections = new ArrayList<EastNorth>();
+            possibleMoveDirections.add(new EastNorth(
+                    initialN1en.getY() - initialN2en.getY(),
+                    initialN2en.getX() - initialN1en.getX()));
+
+            //add directions parallel to neighbor segments
+
+            Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
+            if (prevNode != null) {
+                EastNorth en = prevNode.getEastNorth();
+                possibleMoveDirections.add(new EastNorth(
+                        initialN1en.getX() - en.getX(),
+                        initialN1en.getY() - en.getY()));
+            }
+
+            Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
+            if (nextNode != null) {
+                EastNorth en = nextNode.getEastNorth();
+                possibleMoveDirections.add(new EastNorth(
+                        initialN2en.getX() - en.getX(),
+                        initialN2en.getY() - en.getY()));
+            }
+            
+            // Signifies that nothing has happened yet
+            newN1en = null;
+            newN2en = null;
+            moveCommand = null;
+
+            Main.map.mapView.addTemporaryLayer(this);
+
+            updateStatusLine();
+            Main.map.mapView.repaint();
+
+            // Make note of time pressed
+            mouseDownTime = System.currentTimeMillis();
+
+            // Make note of mouse position
+            initialMousePos = e.getPoint();
+        }
     }
 
@@ -137,99 +221,250 @@
 
         // do not count anything as a drag if it lasts less than 100 milliseconds.
-        if (System.currentTimeMillis() - mouseDownTime < initialMoveDelay) return;
+        if (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)
+            return;
 
         if (mode == Mode.select) {
             // Just sit tight and wait for mouse to be released.
         } else {
-            Node nd1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
-            Node nd2 = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1);
-
-            EastNorth en1 = nd1.getEastNorth();
-            EastNorth en2 = nd2.getEastNorth();
-            EastNorth en3 = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
-
-            double u = ((en3.east() - en1.east()) * (en2.east() - en1.east()) +
-                    (en3.north() - en1.north()) * (en2.north() - en1.north())) /
-                    en2.distanceSq(en1);
-            // the point on the segment from which the distance to mouse pos is shortest
-            EastNorth base = new EastNorth(en1.east() + u * (en2.east() - en1.east()),
-                    en1.north() + u * (en2.north() - en1.north()));
-
-            // find out the distance, in metres, between the base point and the mouse cursor
-            double distance = Main.proj.eastNorth2latlon(base).greatCircleDistance(Main.proj.eastNorth2latlon(en3));
+            //move and extrude mode - move the selected segment
+
+            EastNorth initialMouseEn = Main.map.mapView.getEastNorth(initialMousePos.x, initialMousePos.y);
+            EastNorth mouseEn = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
+            EastNorth mouseMovement = new EastNorth(mouseEn.getX() - initialMouseEn.getX(), mouseEn.getY() - initialMouseEn.getY());
+
+            double bestDistance = Double.POSITIVE_INFINITY;
+            EastNorth bestMovement = null;
+            activeMoveDirection = null;
+
+            //find the best movement direction and vector
+            for (EastNorth direction: possibleMoveDirections) {
+                EastNorth movement = calculateSegmentOffset(initialN1en, initialN2en, direction , mouseEn);
+                if (movement == null) {
+                    //if direction parallel to segment.
+                    continue;
+                }
+
+                double distanceFromMouseMovement = movement.distance(mouseMovement);
+                if (bestDistance > distanceFromMouseMovement) {
+                    bestDistance = distanceFromMouseMovement;
+                    activeMoveDirection = direction;
+                    bestMovement = movement;
+                }
+            }
+
+            newN1en = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY());
+            newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY());
+
+            // find out the movement distance, in metres
+            double distance = Main.proj.eastNorth2latlon(initialN1en).greatCircleDistance(Main.proj.eastNorth2latlon(newN1en));
             Main.map.statusLine.setDist(distance);
             updateStatusLine();
 
-            // compute vertical and horizontal components.
-            double xoff = en3.east() - base.east();
-            double yoff = en3.north() - base.north();
-
-            newN1en = new EastNorth(en1.getX() + xoff, en1.getY() + yoff);
-            newN2en = new EastNorth(en2.getX() + xoff, en2.getY() + yoff);
-
-            // find out the distance, in metres, between the initial position of N1 and the new one.
-            Main.map.statusLine.setDist(Main.proj.eastNorth2latlon(initialN1en).greatCircleDistance(Main.proj.eastNorth2latlon(newN1en)));
+            setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+
+            if (mode == Mode.extrude) {
+                //nothing here
+            } else if (mode == Mode.translate) {
+                //move nodes to new position
+                if (moveCommand == null) {
+                    //make a new move command
+                    Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>();
+                    nodelist.add(selectedSegment.getFirstNode());
+                    nodelist.add(selectedSegment.getSecondNode());
+                    moveCommand = new MoveCommand(nodelist, bestMovement.getX(), bestMovement.getY());
+                    Main.main.undoRedo.add(moveCommand);
+                } else {
+                    //reuse existing move command
+                    moveCommand.undoCommand();
+                    moveCommand.moveAgain(bestMovement.getX(), bestMovement.getY());
+                }
+            }
+
+            Main.map.mapView.repaint();
+        }
+    }
+
+    /**
+     * Do anything that needs to be done, then switch back to select mode
+     */
+    @Override public void mouseReleased(MouseEvent e) {
+
+        if(!Main.map.mapView.isActiveLayerVisible())
+            return;
+
+        if (mode == Mode.select) {
+            // Nothing to be done
+        } else {
+            if (mode == Mode.extrude) {
+                if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null) {
+                    // create extrusion
+
+                    Collection<Command> cmds = new LinkedList<Command>();
+                    Way wnew = new Way(selectedSegment.way);
+                    int insertionPoint = selectedSegment.lowerIndex + 1;
+
+                    //find if the new points overlap existing segments (in case of 90 degree angles)
+                    Node prevNode = getPreviousNode(selectedSegment.lowerIndex);
+                    boolean nodeOverlapsSegment = prevNode != null && pointsColinear(prevNode.getEastNorth(), initialN1en, newN1en);
+
+                    if (nodeOverlapsSegment && !alwaysCreateNodes) {
+                        //move existing node
+                        Node n1Old = selectedSegment.getFirstNode();
+                        cmds.add(new MoveCommand(n1Old, Main.proj.eastNorth2latlon(newN1en)));
+                    } else {
+                        //introduce new node
+                        Node n1New = new Node(Main.proj.eastNorth2latlon(newN1en));
+                        wnew.addNode(insertionPoint, n1New);
+                        insertionPoint ++;
+                        cmds.add(new AddCommand(n1New));
+                    }
+
+                    //find if the new points overlap existing segments (in case of 90 degree angles)
+                    Node nextNode = getNextNode(selectedSegment.lowerIndex + 1);
+                    nodeOverlapsSegment = nextNode != null && pointsColinear(nextNode.getEastNorth(), initialN2en, newN2en);
+
+                    if (nodeOverlapsSegment && !alwaysCreateNodes) {
+                        //move existing node
+                        Node n2Old = selectedSegment.getSecondNode();
+                        cmds.add(new MoveCommand(n2Old, Main.proj.eastNorth2latlon(newN2en)));
+                    } else {
+                        //introduce new node
+                        Node n2New = new Node(Main.proj.eastNorth2latlon(newN2en));
+                        wnew.addNode(insertionPoint, n2New);
+                        insertionPoint ++;
+                        cmds.add(new AddCommand(n2New));
+                    }
+
+                    //the way was a single segment, close the way
+                    if (wnew.getNodesCount() == 4) {
+                        wnew.addNode(selectedSegment.getFirstNode());
+                    }
+
+                    cmds.add(new ChangeCommand(selectedSegment.way, wnew));
+                    Command c = new SequenceCommand(tr("Extrude Way"), cmds);
+                    Main.main.undoRedo.add(c);
+                }
+            } else if (mode == Mode.translate) {
+                //Commit translate
+                //the move command is already committed in mouseDragged
+            }
+
+            // Switch back into select mode
+            restoreCursor();
+            Main.map.mapView.removeTemporaryLayer(this);
+            selectedSegment = null;
+            moveCommand = null;
+            mode = Mode.select;
+
             updateStatusLine();
-
-            setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
-
-            if (mode == Mode.extrude) {
-
-            } else if (mode == Mode.translate) {
-                Command c = !Main.main.undoRedo.commands.isEmpty()
-                ? Main.main.undoRedo.commands.getLast() : null;
-                if (c instanceof SequenceCommand) {
-                    c = ((SequenceCommand)c).getLastCommand();
-                }
-
-                Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
-                Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
-
-                EastNorth difference = new EastNorth(newN1en.getX()-lastTranslatedN1en.getX(), newN1en.getY()-lastTranslatedN1en.getY());
-
-                // Better way of testing list equality non-order-sensitively?
-                if (c instanceof MoveCommand
-                        && ((MoveCommand)c).getParticipatingPrimitives().contains(n1)
-                        && ((MoveCommand)c).getParticipatingPrimitives().contains(n2)
-                        && ((MoveCommand)c).getParticipatingPrimitives().size() == 2) {
-                    // MoveCommand doesn't let us know how much it has already moved the selection
-                    // so we have to do some ugly record-keeping.
-                    ((MoveCommand)c).moveAgain(difference.getX(), difference.getY());
-                    lastTranslatedN1en = newN1en;
-                } else {
-                    Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>();
-                    nodelist.add(n1);
-                    nodelist.add(n2);
-                    Main.main.undoRedo.add(c = new MoveCommand(nodelist, difference.getX(), difference.getY()));
-                    lastTranslatedN1en = newN1en;
-                }
-            }
             Main.map.mapView.repaint();
         }
     }
 
-    /**
-     * Create a new Line that extends off the edge of the viewport in one direction
-     * @param start The start point of the line
-     * @param unitvector A unit vector denoting the direction of the line
-     * @param g the Graphics2D object  it will be used on
-     */
-    static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) {
-        Rectangle bounds = g.getDeviceConfiguration().getBounds();
-        try {
-            AffineTransform invtrans = g.getTransform().createInverse();
-            Point2D widthpoint = invtrans.deltaTransform(new Point2D.Double(bounds.width,0), null);
-            Point2D heightpoint = invtrans.deltaTransform(new Point2D.Double(0,bounds.height), null);
-
-            // Here we should end up with a gross overestimate of the maximum viewport diagonal in what
-            // Graphics2D calls 'user space'. Essentially a manhattan distance of manhattan distances.
-            // This can be used as a safe length of line to generate which will always go off-viewport.
-            double linelength = Math.abs(widthpoint.getX()) + Math.abs(widthpoint.getY()) + Math.abs(heightpoint.getX()) + Math.abs(heightpoint.getY());
-
-            return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * linelength) , start.getY() + (unitvector.getY() * linelength)));
-        }
-        catch (NoninvertibleTransformException e) {
-            return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * 10) , start.getY() + (unitvector.getY() * 10)));
-        }
+    /***
+     * This method calculates offset amount by witch to move the given segment perpendicularly for it to be in line with mouse position.
+     * @param segmentP1
+     * @param segmentP2
+     * @param targetPos
+     * @return offset amount of P1 and P2.
+     */
+    private static EastNorth calculateSegmentOffset(EastNorth segmentP1, EastNorth segmentP2 ,EastNorth moveDirection , EastNorth targetPos)
+    {
+        EastNorth intersectionPoint = getLineLineIntersection(
+                segmentP1,
+                segmentP2,
+                targetPos,
+                new EastNorth(targetPos.getX() + moveDirection.getX(), targetPos.getY() + moveDirection.getY()));
+
+        if (intersectionPoint == null)
+            return null;
+        else
+            //return distance form base to target position
+            return new EastNorth(targetPos.getX() - intersectionPoint.getX(), targetPos.getY() - intersectionPoint.getY());
+    }
+
+    /**
+     * Finds the intersection of two lines of inifinite length
+     * @return EastNorth null if no intersection was found, the Lon coordinates of the intersection otherwise
+     */
+    static public EastNorth getLineLineIntersection(
+            EastNorth p1, EastNorth p2,
+            EastNorth p3, EastNorth p4) {
+
+        // Convert line from (point, point) form to ax+by=c
+        double a1 = p2.getY() - p1.getY();
+        double b1 = p1.getX() - p2.getX();
+        double c1 = p2.getX() * p1.getY() - p1.getX() * p2.getY();
+
+        double a2 = p4.getY() - p3.getY();
+        double b2 = p3.getX() - p4.getX();
+        double c2 = p4.getX() * p3.getY() - p3.getX() * p4.getY();
+
+        // Solve the equations
+        double det = a1*b2 - a2*b1;
+        if(det == 0) return null; // Lines are parallel
+
+        return new EastNorth(
+                (b1*c2 - b2*c1)/det,
+                (a2*c1 - a1*c2)/det);
+    }
+
+    /**
+     * Returns true if all points are on the same line.
+     */
+    private static boolean pointsColinear(EastNorth p1, EastNorth p2, EastNorth p3) {
+
+        //the simple dumb way of triangle side lengths.
+        double distance1 = p1.distance(p2);
+        double distance2 = p1.distance(p3);
+        double distance3 = p2.distance(p3);
+
+        //sort so that distance 1 is the greatest
+        if (distance1 < distance2) {
+            double temp = distance1;
+            distance1 = distance2;
+            distance2 = temp;
+        }
+
+        if (distance1 < distance3) {
+            double temp = distance1;
+            distance1 = distance3;
+            distance3 = temp;
+        }
+
+        //test with some treshold
+        double difference = distance1 - distance2 - distance3;
+
+        return (Math.abs(difference) < 1e-15);
+
+    }
+
+    /**
+     * Gets a node from selected way before given index.
+     * @param index  index of current node
+     * @return previous node or null if there are no nodes there.
+     */
+    private Node getPreviousNode(int index) {
+        if (index > 0)
+            return selectedSegment.way.getNode(index - 1);
+        else if (selectedSegment.way.isClosed())
+            return selectedSegment.way.getNode(selectedSegment.way.getNodesCount() - 2);
+        else
+            return null;
+    }
+
+    /**
+     * Gets a node from selected way before given index.
+     * @param index index of current node
+     * @return next node or null if there are no nodes there.
+     */
+    private Node getNextNode(int index) {
+        int count = selectedSegment.way.getNodesCount();
+        if (index <  count - 1)
+            return selectedSegment.way.getNode(index + 1);
+        else if (selectedSegment.way.isClosed())
+            return selectedSegment.way.getNode(1);
+        else
+            return null;
     }
 
@@ -264,39 +499,44 @@
                     g2.draw(oldline);
 
-                    EastNorth segmentVector = new EastNorth(initialN2en.getX()-initialN1en.getX(), initialN2en.getY()-initialN1en.getY());
-
-                    double fac = 1.0 / Math.hypot(segmentVector.getX(), segmentVector.getY());
-                    // swap coords to get normal, mult by factor to get unit vector.
-                    EastNorth normalUnitVector = new EastNorth(segmentVector.getY() * fac, segmentVector.getX() * fac);
-
-                    // Draw a guideline along the normal.
-                    Line2D normline;
-                    Point2D centerpoint = new Point2D.Double((p1.getX()+p2.getX())*0.5, (p1.getY()+p2.getY())*0.5);
-                    EastNorth drawnorm;
-                    // Check to see if our new N1 is in a positive direction with respect to the normalUnitVector.
-                    // Even if the x component is zero, we should still be able to discern using +0.0 and -0.0
-                    if (newN1en == null || (newN1en.getX() > initialN1en.getX() == normalUnitVector.getX() > -0.0)) {
-                        drawnorm = normalUnitVector;
-                    } else {
-                        // If not, use a sign-flipped version of the normalUnitVector.
-                        drawnorm = new EastNorth(-normalUnitVector.getX(), -normalUnitVector.getY());
+                    if (activeMoveDirection != null) {
+
+                        double fac = 1.0 / activeMoveDirection.distance(0,0);
+                        // mult by factor to get unit vector.
+                        EastNorth normalUnitVector = new EastNorth(activeMoveDirection.getX() * fac, activeMoveDirection.getY() * fac);
+
+                        // Check to see if our new N1 is in a positive direction with respect to the normalUnitVector.
+                        // Even if the x component is zero, we should still be able to discern using +0.0 and -0.0
+                        if (newN1en != null && (newN1en.getX() > initialN1en.getX() != normalUnitVector.getX() > -0.0)) {
+                            // If not, use a sign-flipped version of the normalUnitVector.
+                            normalUnitVector = new EastNorth(-normalUnitVector.getX(), -normalUnitVector.getY());
+                        }
+
+                        //HACK: swap Y, because the target pixels are top down, but EastNorth is bottom-up.
+                        //This is normally done by MapView.getPoint, but it does not work on vectors.
+                        normalUnitVector.setLocation(normalUnitVector.getX(), -normalUnitVector.getY());
+
+                        // Draw a guideline along the normal.
+                        Line2D normline;
+                        Point2D centerpoint = new Point2D.Double((p1.getX()+p2.getX())*0.5, (p1.getY()+p2.getY())*0.5);
+                        normline = createSemiInfiniteLine(centerpoint, normalUnitVector, g2);
+                        g2.draw(normline);
+
+                        // Draw right angle marker on initial position, only when moving at right angle
+                        if (activeMoveDirection == possibleMoveDirections.get(0)) {
+                            // EastNorth units per pixel
+                            double factor = 1.0/g2.getTransform().getScaleX();
+
+                            double raoffsetx = 8.0*factor*normalUnitVector.getX();
+                            double raoffsety = 8.0*factor*normalUnitVector.getY();
+                            Point2D ra1 = new Point2D.Double(centerpoint.getX()+raoffsetx, centerpoint.getY()+raoffsety);
+                            Point2D ra3 = new Point2D.Double(centerpoint.getX()-raoffsety, centerpoint.getY()+raoffsetx);
+                            Point2D ra2 = new Point2D.Double(ra1.getX()-raoffsety, ra1.getY()+raoffsetx);
+                            GeneralPath ra = new GeneralPath();
+                            ra.moveTo((float)ra1.getX(), (float)ra1.getY());
+                            ra.lineTo((float)ra2.getX(), (float)ra2.getY());
+                            ra.lineTo((float)ra3.getX(), (float)ra3.getY());
+                            g2.draw(ra);
+                        }
                     }
-                    normline = createSemiInfiniteLine(centerpoint, drawnorm, g2);
-                    g2.draw(normline);
-
-                    // EastNorth units per pixel
-                    double factor = 1.0/g2.getTransform().getScaleX();
-
-                    // Draw right angle marker on initial position.
-                    double raoffsetx = 8.0*factor*drawnorm.getX();
-                    double raoffsety = 8.0*factor*drawnorm.getY();
-                    Point2D ra1 = new Point2D.Double(centerpoint.getX()+raoffsetx, centerpoint.getY()+raoffsety);
-                    Point2D ra3 = new Point2D.Double(centerpoint.getX()-raoffsety, centerpoint.getY()+raoffsetx);
-                    Point2D ra2 = new Point2D.Double(ra1.getX()-raoffsety, ra1.getY()+raoffsetx);
-                    GeneralPath ra = new GeneralPath();
-                    ra.moveTo((float)ra1.getX(), (float)ra1.getY());
-                    ra.lineTo((float)ra2.getX(), (float)ra2.getY());
-                    ra.lineTo((float)ra3.getX(), (float)ra3.getY());
-                    g2.draw(ra);
                 }
             }
@@ -305,114 +545,48 @@
 
     /**
-     * If the left mouse button is pressed over a segment, switch
-     * to either extrude or translate mode depending on whether ctrl is held.
-     */
-    @Override public void mousePressed(MouseEvent e) {
-        if(!Main.map.mapView.isActiveLayerVisible())
-            return;
-        if (!(Boolean)this.getValue("active")) return;
-        if (e.getButton() != MouseEvent.BUTTON1)
-            return;
-        // boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
-        // boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
-        // boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
-
-        selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
-
-        if (selectedSegment == null) {
-            // If nothing gets caught, stay in select mode
-        } else {
-            // Otherwise switch to another mode
-
-            // For extrusion, these positions are actually never changed,
-            // but keeping note of this anyway allows us to not continually
-            // look it up and also allows us to unify code with the translate mode
-            initialN1en = selectedSegment.way.getNode(selectedSegment.lowerIndex).getEastNorth();
-            initialN2en = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1).getEastNorth();
-
-            // Signifies that nothing has happened yet
-            newN1en = null;
-            newN2en = null;
-
-            Main.map.mapView.addTemporaryLayer(this);
-
-            updateStatusLine();
-            Main.map.mapView.repaint();
-
-            // Make note of time pressed
-            mouseDownTime = System.currentTimeMillis();
-
-            // Make note of mouse position
-            initialMousePos = e.getPoint();
-
-            // Switch mode.
-            if ( (e.getModifiers() & ActionEvent.CTRL_MASK) != 0 ) {
-                mode = Mode.translate;
-                lastTranslatedN1en = initialN1en;
-            } else {
-                mode = Mode.extrude;
-                getCurrentDataSet().setSelected(selectedSegment.way);
-            }
-        }
-    }
-
-    /**
-     * Do anything that needs to be done, then switch back to select mode
-     */
-    @Override public void mouseReleased(MouseEvent e) {
-
-        if(!Main.map.mapView.isActiveLayerVisible())
-            return;
-
-        if (mode == Mode.select) {
-            // Nothing to be done
-        } else {
-            if (mode == Mode.extrude) {
-                if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null) {
-                    // Commit extrusion
-
-                    Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
-                    //Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
-                    Node n3 = new Node(Main.proj.eastNorth2latlon(newN2en));
-                    Node n4 = new Node(Main.proj.eastNorth2latlon(newN1en));
-                    Way wnew = new Way(selectedSegment.way);
-                    wnew.addNode(selectedSegment.lowerIndex+1, n3);
-                    wnew.addNode(selectedSegment.lowerIndex+1, n4);
-                    if (wnew.getNodesCount() == 4) {
-                        wnew.addNode(n1);
-                    }
-                    Collection<Command> cmds = new LinkedList<Command>();
-                    cmds.add(new AddCommand(n4));
-                    cmds.add(new AddCommand(n3));
-                    cmds.add(new ChangeCommand(selectedSegment.way, wnew));
-                    Command c = new SequenceCommand(tr("Extrude Way"), cmds);
-                    Main.main.undoRedo.add(c);
-                }
-            } else if (mode == Mode.translate) {
-                // I don't think there's anything to do
-            }
-
-            // Switch back into select mode
-            restoreCursor();
-            Main.map.mapView.removeTemporaryLayer(this);
-            selectedSegment = null;
-            mode = Mode.select;
-
-            updateStatusLine();
-            Main.map.mapView.repaint();
-        }
-    }
-
-    @Override public String getModeHelpText() {
-        if (mode == Mode.translate)
-            return tr("Move a segment along its normal, then release the mouse button.");
-        else if (mode == Mode.extrude)
-            return tr("Draw a rectangle of the desired size, then release the mouse button.");
-        else
-            return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal.");
-    }
-
-    @Override public boolean layerIsSupported(Layer l) {
-        return l instanceof OsmDataLayer;
+     * Create a new Line that extends off the edge of the viewport in one direction
+     * @param start The start point of the line
+     * @param unitvector A unit vector denoting the direction of the line
+     * @param g the Graphics2D object  it will be used on
+     */
+    static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) {
+        Rectangle bounds = g.getDeviceConfiguration().getBounds();
+        try {
+            AffineTransform invtrans = g.getTransform().createInverse();
+            Point2D widthpoint = invtrans.deltaTransform(new Point2D.Double(bounds.width,0), null);
+            Point2D heightpoint = invtrans.deltaTransform(new Point2D.Double(0,bounds.height), null);
+
+            // Here we should end up with a gross overestimate of the maximum viewport diagonal in what
+            // Graphics2D calls 'user space'. Essentially a manhattan distance of manhattan distances.
+            // This can be used as a safe length of line to generate which will always go off-viewport.
+            double linelength = Math.abs(widthpoint.getX()) + Math.abs(widthpoint.getY()) + Math.abs(heightpoint.getX()) + Math.abs(heightpoint.getY());
+
+            return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * linelength) , start.getY() + (unitvector.getY() * linelength)));
+        }
+        catch (NoninvertibleTransformException e) {
+            return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * 10) , start.getY() + (unitvector.getY() * 10)));
+        }
+    }
+
+    private static Cursor getCursor(String name, String mod, int def) {
+        try {
+            return ImageProvider.getCursor(name, mod);
+        } catch (Exception e) {
+        }
+        return Cursor.getPredefinedCursor(def);
+    }
+
+    private void setCursor(Cursor c) {
+        if (oldCursor == null) {
+            oldCursor = Main.map.mapView.getCursor();
+            Main.map.mapView.setCursor(c);
+        }
+    }
+
+    private void restoreCursor() {
+        if (oldCursor != null) {
+            Main.map.mapView.setCursor(oldCursor);
+            oldCursor = null;
+        }
     }
 }
Index: /trunk/src/org/openstreetmap/josm/data/osm/WaySegment.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/WaySegment.java	(revision 3556)
+++ /trunk/src/org/openstreetmap/josm/data/osm/WaySegment.java	(revision 3557)
@@ -22,4 +22,12 @@
     }
 
+    public Node getFirstNode(){
+        return way.getNode(lowerIndex);
+    }
+
+    public Node getSecondNode(){
+        return way.getNode(lowerIndex + 1);
+    }
+
     @Override public boolean equals(Object o) {
         return o != null && o instanceof WaySegment
