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 [email protected] http://lists.openstreetmap.org/listinfo/josm-dev
