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) {
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