Hi,

Attached is a patch against SVN 2374 which adds a feature to extrude mode: hold 
ctrl while dragging to move an existing segment along its normal rather than 
creating a new segment.

This is needed to be able to easily edit buildings etc after they have been 
initially extruded.

In adding this, I touched most of the extrude mode code, removing some of the 
more glaring problems (like editing logic in the painting code) and tried to 
sanitize the mode's state machine. I also removed most of the remnants of the 
select mode it was copied from for clarity.

There are a few other things I would like to do to extrude mode, such as make 
the geometry work in real space rather then screen space - so that it's 
projection independent. And I also want to make the hint line painting a bit 
better. But this will have to do for now.

Please note I am a c++ programmer, not a java programmer, so comments are 
welcome.


robert.
Index: src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java
===================================================================
--- src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java	(revision 2374)
+++ src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java	(working copy)
@@ -12,7 +12,10 @@
 import java.awt.Point;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
+import java.awt.event.ActionEvent;
 import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.Line2D.Double;
 import java.util.Collection;
 import java.util.LinkedList;
 
@@ -20,9 +23,11 @@
 import org.openstreetmap.josm.command.AddCommand;
 import org.openstreetmap.josm.command.ChangeCommand;
 import org.openstreetmap.josm.command.Command;
+import org.openstreetmap.josm.command.MoveCommand;
 import org.openstreetmap.josm.command.SequenceCommand;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.data.osm.Node;
+import org.openstreetmap.josm.data.osm.OsmPrimitive;
 import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.WaySegment;
 import org.openstreetmap.josm.gui.MapFrame;
@@ -35,14 +40,11 @@
 
 /**
  * Makes a rectangle from a line, or modifies a rectangle.
- *
- * This class currently contains some "sleeping" code copied from DrawAction (move and rotate)
- * which can eventually be removed, but it may also get activated here and removed in DrawAction.
  */
 public class ExtrudeAction extends MapMode implements MapViewPaintable {
 
-    enum Mode { EXTRUDE, rotate, select }
-    private Mode mode = null;
+    enum Mode { extrude, translate, select }
+    private Mode mode = Mode.select;
     private long mouseDownTime = 0;
     private WaySegment selectedSegment = null;
     private Color selectedColor;
@@ -56,10 +58,6 @@
      */
     private Cursor oldCursor;
     /**
-     * The current position of the mouse
-     */
-    private Point mousePos;
-    /**
      * The position of the mouse cursor when the drag action was initiated.
      */
     private Point initialMousePos;
@@ -67,7 +65,17 @@
      * The time which needs to pass between click and release before something
      * counts as a move, in milliseconds
      */
-    private int initialMoveDelay = 200;
+    private static int initialMoveDelay = 200;
+    /**
+     * For translation, a the initial EastNorths of node1 and node2
+     */
+    private EastNorth initialN1en;
+    private EastNorth initialN2en;
+    /**
+     * This is to work around some deficiencies in MoveCommand when translating
+     */
+    private double alreadyTranslatedX;
+    private double alreadyTranslatedY;
 
     /**
      * Create a new SelectAction
@@ -116,7 +124,6 @@
         Main.map.mapView.removeMouseListener(this);
         Main.map.mapView.removeMouseMotionListener(this);
         Main.map.mapView.removeTemporaryLayer(this);
-
     }
 
     /**
@@ -127,41 +134,22 @@
     @Override public void mouseDragged(MouseEvent e) {
         if(!Main.map.mapView.isActiveLayerVisible())
             return;
-        if (mode == Mode.select) return;
 
-        // do not count anything as a move if it lasts less than 100 milliseconds.
-        if ((mode == Mode.EXTRUDE) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)) return;
+        // do not count anything as a drag if it lasts less than 100 milliseconds.
+        if (System.currentTimeMillis() - mouseDownTime < initialMoveDelay) return;
 
-        if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0)
-            return;
+        if (mode == Mode.select) {
+            // Just sit tight and wait for mouse to be released.
+        } else {
+            EastNorth en1 = initialN1en;
+            EastNorth en2 = initialN2en;
+            // This may be ugly, but I can't see any other way of getting a mapview from here.
+            EastNorth en3 = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y);
 
-        if (mode == Mode.EXTRUDE) {
-            setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
-        }
-
-        if (mousePos == null) {
-            mousePos = e.getPoint();
-            return;
-        }
-
-        Main.map.mapView.repaint();
-        mousePos = e.getPoint();
-
-    }
-
-    public void paint(Graphics g, MapView mv) {
-        if (selectedSegment != null) {
-            Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
-            Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1);
-
-            EastNorth en1 = n1.getEastNorth();
-            EastNorth en2 = n2.getEastNorth();
-            EastNorth en3 = mv.getEastNorth(mousePos.x, mousePos.y);
-
+            // calculate base - the point on the segment from which the distance to mouse pos is shortest
             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()));
 
@@ -174,19 +162,71 @@
             xoff = en3.east() - base.east();
             yoff = en3.north() - base.north();
 
+            Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
+            Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
+
+            if (mode == Mode.extrude) {
+                // This doesn't seem to work so should it be here?
+                setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+            } 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();
+                }
+
+                // Better way of testing list equality non-order-sensitively?
+                if (c instanceof MoveCommand
+                && ((MoveCommand)c).getMovedNodes().contains(n1)
+                && ((MoveCommand)c).getMovedNodes().contains(n2)
+                && ((MoveCommand)c).getMovedNodes().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.
+                    double dx = xoff - alreadyTranslatedX;
+                    double dy = yoff - alreadyTranslatedY;
+                    ((MoveCommand)c).moveAgain(dx,dy);
+                    alreadyTranslatedX += dx;
+                    alreadyTranslatedY += dy;
+                } else {
+                    Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>();
+                    nodelist.add(n1);
+                    nodelist.add(n2);
+                    Main.main.undoRedo.add(c = new MoveCommand(nodelist, xoff, yoff));
+                    alreadyTranslatedX += xoff;
+                    alreadyTranslatedY += yoff;
+                }
+            }
+            Main.map.mapView.repaint();
+        }
+    }
+
+    public void paint(Graphics g, MapView mv) {
+        if (mode == Mode.select) {
+            // Nothing to do
+        } else {
             Graphics2D g2 = (Graphics2D)g;
             g2.setColor(selectedColor);
             g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
             GeneralPath b = new GeneralPath();
-            Point p1 = mv.getPoint(en1);
-            Point p2 = mv.getPoint(en2);
-            Point p3 = mv.getPoint(en1.add(xoff, yoff));
-            Point p4 = mv.getPoint(en2.add(xoff, yoff));
+            Point p1 = mv.getPoint(initialN1en);
+            Point p2 = mv.getPoint(initialN2en);
+            Point p3 = mv.getPoint(initialN1en.add(xoff, yoff));
+            Point p4 = mv.getPoint(initialN2en.add(xoff, yoff));
 
-            b.moveTo(p1.x, p1.y); b.lineTo(p3.x, p3.y);
-            b.lineTo(p4.x, p4.y); b.lineTo(p2.x, p2.y);
-            b.lineTo(p1.x, p1.y);
-            g2.draw(b);
+            if (mode == Mode.extrude) {
+                b.moveTo(p1.x, p1.y); b.lineTo(p3.x, p3.y);
+                b.lineTo(p4.x, p4.y); b.lineTo(p2.x, p2.y);
+                b.lineTo(p1.x, p1.y);
+                g2.draw(b);
+                g2.setStroke(new BasicStroke(1));
+            } else if (mode == Mode.translate) {
+                Line2D newline = new Line2D.Double(p3, p4);
+                g2.draw(newline);
+                g2.setStroke(new BasicStroke(2));
+                Line2D oldline = new Line2D.Double(p1, p2);
+                g2.draw(oldline);
+            }
+
             g2.setStroke(new BasicStroke(1));
         }
     }
@@ -203,23 +243,41 @@
         // boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0;
         // boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0;
 
-        mouseDownTime = System.currentTimeMillis();
+        selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint());
 
-        selectedSegment =
-            Main.map.mapView.getNearestWaySegment(e.getPoint());
+        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;
+                alreadyTranslatedX = 0.0;
+                alreadyTranslatedY = 0.0;
+            } else {
+                mode = Mode.extrude;
+                getCurrentDataSet().setSelected(selectedSegment.way);
+            }
 
-        mode = (selectedSegment == null) ? Mode.select : Mode.EXTRUDE;
-        oldCursor = Main.map.mapView.getCursor();
+            // 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();
 
-        updateStatusLine();
-        Main.map.mapView.addTemporaryLayer(this);
-        Main.map.mapView.repaint();
+            oldCursor = Main.map.mapView.getCursor();
+            Main.map.mapView.addTemporaryLayer(this);
 
-        mousePos = e.getPoint();
-        initialMousePos = e.getPoint();
+            updateStatusLine();
+            Main.map.mapView.repaint();
 
-        if(selectedSegment != null) {
-            getCurrentDataSet().setSelected(selectedSegment.way);
+            // Make note of time pressed
+            mouseDownTime = System.currentTimeMillis();
+
+            // Make note of mouse position
+            initialMousePos = e.getPoint();
+
+            xoff = 0;
+            yoff = 0;
         }
     }
 
@@ -227,47 +285,60 @@
      * Restore the old mouse cursor.
      */
     @Override public void mouseReleased(MouseEvent e) {
+
         if(!Main.map.mapView.isActiveLayerVisible())
             return;
-        restoreCursor();
-        if (selectedSegment == null) return;
-        if (mousePos.distance(initialMousePos) > 10) {
-            Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
-            Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
-            EastNorth en3 = n2.getEastNorth().add(xoff, yoff);
-            Node n3 = new Node(Main.proj.eastNorth2latlon(en3));
-            EastNorth en4 = n1.getEastNorth().add(xoff, yoff);
-            Node n4 = new Node(Main.proj.eastNorth2latlon(en4));
-            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);
+
+        if (mode == mode.select) {
+            // Nothing to be done
+        } else {
+            if (mode == mode.extrude) {
+                if (e.getPoint().distance(initialMousePos) > 10) {
+                    // Commit extrusion
+
+                    Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex);
+                    Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1);
+                    EastNorth en3 = n2.getEastNorth().add(xoff, yoff);
+                    Node n3 = new Node(Main.proj.eastNorth2latlon(en3));
+                    EastNorth en4 = n1.getEastNorth().add(xoff, yoff);
+                    Node n4 = new Node(Main.proj.eastNorth2latlon(en4));
+                    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
             }
-            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);
+
+            // Switch back into select mode
+            restoreCursor();
+            Main.map.mapView.removeTemporaryLayer(this);
+            selectedSegment = null;
+            mode = Mode.select;
+
+            updateStatusLine();
+            Main.map.mapView.repaint();
         }
-
-        Main.map.mapView.removeTemporaryLayer(this);
-        selectedSegment = null;
-        mode = null;
-        updateStatusLine();
-        Main.map.mapView.repaint();
     }
 
     @Override public String getModeHelpText() {
         if (mode == Mode.select)
-            return tr("Release the mouse button to select the objects in the rectangle.");
-        else if (mode == Mode.EXTRUDE)
+            return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal.");
+        else if (mode == Mode.extrude)
             return tr("Draw a rectangle of the desired size, then release the mouse button.");
-        else if (mode == Mode.rotate)
-            return tr("Release the mouse button to stop rotating.");
+        else if (mode == Mode.translate)
+            return tr("Move a segment along its normal.");
         else
-            return tr("Drag a way segment to make a rectangle.");
+            return tr("");
     }
 
     @Override public boolean layerIsSupported(Layer l) {

Attachment: signature.asc
Description: This is a digitally signed message part.

_______________________________________________
josm-dev mailing list
josm-dev@openstreetmap.org
http://lists.openstreetmap.org/listinfo/josm-dev

Reply via email to