- Timestamp:
- 2009-11-10T18:11:04+01:00 (15 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java
r1898 r2429 11 11 import java.awt.Graphics2D; 12 12 import java.awt.Point; 13 import java.awt.Rectangle; 13 14 import java.awt.event.KeyEvent; 14 15 import java.awt.event.MouseEvent; 16 import java.awt.event.ActionEvent; 17 import java.awt.geom.AffineTransform; 18 import java.awt.geom.NoninvertibleTransformException; 15 19 import java.awt.geom.GeneralPath; 20 import java.awt.geom.Point2D; 21 import java.awt.geom.Line2D; 22 import java.awt.geom.Line2D.Double; 23 import java.lang.Math; 16 24 import java.util.Collection; 17 25 import java.util.LinkedList; … … 21 29 import org.openstreetmap.josm.command.ChangeCommand; 22 30 import org.openstreetmap.josm.command.Command; 31 import org.openstreetmap.josm.command.MoveCommand; 23 32 import org.openstreetmap.josm.command.SequenceCommand; 24 33 import org.openstreetmap.josm.data.coor.EastNorth; 25 34 import org.openstreetmap.josm.data.osm.Node; 35 import org.openstreetmap.josm.data.osm.OsmPrimitive; 26 36 import org.openstreetmap.josm.data.osm.Way; 27 37 import org.openstreetmap.josm.data.osm.WaySegment; … … 36 46 /** 37 47 * Makes a rectangle from a line, or modifies a rectangle. 38 *39 * This class currently contains some "sleeping" code copied from DrawAction (move and rotate)40 * which can eventually be removed, but it may also get activated here and removed in DrawAction.41 48 */ 42 49 public class ExtrudeAction extends MapMode implements MapViewPaintable { 43 50 44 enum Mode { EXTRUDE, rotate, select }45 private Mode mode = null;51 enum Mode { extrude, translate, select } 52 private Mode mode = Mode.select; 46 53 private long mouseDownTime = 0; 47 54 private WaySegment selectedSegment = null; 48 55 private Color selectedColor; 49 56 50 double xoff;51 double yoff;52 double distance;53 54 57 /** 55 58 * The old cursor before the user pressed the mouse button. 56 59 */ 57 60 private Cursor oldCursor; 58 /**59 * The current position of the mouse60 */61 private Point mousePos;62 61 /** 63 62 * The position of the mouse cursor when the drag action was initiated. … … 68 67 * counts as a move, in milliseconds 69 68 */ 70 private int initialMoveDelay = 200; 69 private static int initialMoveDelay = 200; 70 /** 71 * The initial EastNorths of node1 and node2 72 */ 73 private EastNorth initialN1en; 74 private EastNorth initialN2en; 75 /** 76 * The new EastNorths of node1 and node2 77 */ 78 private EastNorth newN1en; 79 private EastNorth newN2en; 80 /** 81 * This is to work around some deficiencies in MoveCommand when translating 82 */ 83 private EastNorth lastTranslatedN1en; 84 /** 85 * Normal unit vector of the selected segment. 86 */ 87 private EastNorth normalUnitVector; 88 /** 89 * Vector of node2 from node1. 90 */ 91 private EastNorth segmentVector; 92 /** 93 * Transforms the mouse point (in EastNorth space) to the normal-shifted position 94 * of point 1 of the selectedSegment. 95 */ 96 private AffineTransform normalTransform; 71 97 72 98 /** … … 117 143 Main.map.mapView.removeMouseMotionListener(this); 118 144 Main.map.mapView.removeTemporaryLayer(this); 119 120 } 121 122 /** 123 * If the left mouse button is pressed, move all currently selected 124 * objects (if one of them is under the mouse) or the current one under the 125 * mouse (which will become selected). 145 } 146 147 /** 148 * Perform action depending on what mode we're in. 126 149 */ 127 150 @Override public void mouseDragged(MouseEvent e) { 128 151 if(!Main.map.mapView.isActiveLayerVisible()) 129 152 return; 130 if (mode == Mode.select) return; 131 132 // do not count anything as a move if it lasts less than 100 milliseconds. 133 if ((mode == Mode.EXTRUDE) && (System.currentTimeMillis() - mouseDownTime < initialMoveDelay)) return; 134 135 if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0) 136 return; 137 138 if (mode == Mode.EXTRUDE) { 153 154 // do not count anything as a drag if it lasts less than 100 milliseconds. 155 if (System.currentTimeMillis() - mouseDownTime < initialMoveDelay) return; 156 157 if (mode == Mode.select) { 158 // Just sit tight and wait for mouse to be released. 159 } else { 160 // This may be ugly, but I can't see any other way of getting a mapview from here. 161 EastNorth mouseen = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y); 162 163 Point2D newN1point = normalTransform.transform(mouseen, null); 164 165 newN1en = new EastNorth(newN1point.getX(), newN1point.getY()); 166 newN2en = newN1en.add(segmentVector.getX(), segmentVector.getY()); 167 168 // find out the distance, in metres, between the initial position of N1 and the new one. 169 Main.map.statusLine.setDist(Main.proj.eastNorth2latlon(initialN1en).greatCircleDistance(Main.proj.eastNorth2latlon(newN1en))); 170 updateStatusLine(); 171 139 172 setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); 140 } 141 142 if (mousePos == null) { 143 mousePos = e.getPoint(); 144 return; 145 } 146 147 Main.map.mapView.repaint(); 148 mousePos = e.getPoint(); 149 173 174 if (mode == Mode.extrude) { 175 176 } else if (mode == Mode.translate) { 177 Command c = !Main.main.undoRedo.commands.isEmpty() 178 ? Main.main.undoRedo.commands.getLast() : null; 179 if (c instanceof SequenceCommand) { 180 c = ((SequenceCommand)c).getLastCommand(); 181 } 182 183 Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex); 184 Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1); 185 186 EastNorth difference = new EastNorth(newN1en.getX()-lastTranslatedN1en.getX(), newN1en.getY()-lastTranslatedN1en.getY()); 187 188 // Better way of testing list equality non-order-sensitively? 189 if (c instanceof MoveCommand 190 && ((MoveCommand)c).getMovedNodes().contains(n1) 191 && ((MoveCommand)c).getMovedNodes().contains(n2) 192 && ((MoveCommand)c).getMovedNodes().size() == 2) { 193 // MoveCommand doesn't let us know how much it has already moved the selection 194 // so we have to do some ugly record-keeping. 195 ((MoveCommand)c).moveAgain(difference.getX(), difference.getY()); 196 lastTranslatedN1en = newN1en; 197 } else { 198 Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>(); 199 nodelist.add(n1); 200 nodelist.add(n2); 201 Main.main.undoRedo.add(c = new MoveCommand(nodelist, difference.getX(), difference.getY())); 202 lastTranslatedN1en = newN1en; 203 } 204 } 205 Main.map.mapView.repaint(); 206 } 207 } 208 209 /** 210 * Create a new Line that extends off the edge of the viewport in one direction 211 * @param start The start point of the line 212 * @param unitvector A unit vector denoting the direction of the line 213 * @param g the Graphics2D object it will be used on 214 */ 215 static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) { 216 Rectangle bounds = g.getDeviceConfiguration().getBounds(); 217 try { 218 AffineTransform invtrans = g.getTransform().createInverse(); 219 Point2D widthpoint = invtrans.deltaTransform(new Point2D.Double(bounds.width,0), null); 220 Point2D heightpoint = invtrans.deltaTransform(new Point2D.Double(0,bounds.height), null); 221 222 // Here we should end up with a gross overestimate of the maximum viewport diagonal in what 223 // Graphics2D calls 'user space'. Essentially a manhattan distance of manhattan distances. 224 // This can be used as a safe length of line to generate which will always go off-viewport. 225 double linelength = Math.abs(widthpoint.getX()) + Math.abs(widthpoint.getY()) + Math.abs(heightpoint.getX()) + Math.abs(heightpoint.getY()); 226 227 return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * linelength) , start.getY() + (unitvector.getY() * linelength))); 228 } 229 catch (NoninvertibleTransformException e) { 230 return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * 10) , start.getY() + (unitvector.getY() * 10))); 231 } 150 232 } 151 233 152 234 public void paint(Graphics g, MapView mv) { 153 if (selectedSegment != null) { 154 Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex); 155 Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1); 156 157 EastNorth en1 = n1.getEastNorth(); 158 EastNorth en2 = n2.getEastNorth(); 159 EastNorth en3 = mv.getEastNorth(mousePos.x, mousePos.y); 160 161 double u = ((en3.east() - en1.east()) * (en2.east() - en1.east()) + 162 (en3.north() - en1.north()) * (en2.north() - en1.north())) / 163 en2.distanceSq(en1); 164 // the point on the segment from which the distance to mouse pos is shortest 165 EastNorth base = new EastNorth(en1.east() + u * (en2.east() - en1.east()), 166 en1.north() + u * (en2.north() - en1.north())); 167 168 // find out the distance, in metres, between the base point and the mouse cursor 169 distance = Main.proj.eastNorth2latlon(base).greatCircleDistance(Main.proj.eastNorth2latlon(en3)); 170 Main.map.statusLine.setDist(distance); 171 updateStatusLine(); 172 173 // compute vertical and horizontal components. 174 xoff = en3.east() - base.east(); 175 yoff = en3.north() - base.north(); 176 177 Graphics2D g2 = (Graphics2D)g; 178 g2.setColor(selectedColor); 179 g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); 180 GeneralPath b = new GeneralPath(); 181 Point p1 = mv.getPoint(en1); 182 Point p2 = mv.getPoint(en2); 183 Point p3 = mv.getPoint(en1.add(xoff, yoff)); 184 Point p4 = mv.getPoint(en2.add(xoff, yoff)); 185 186 b.moveTo(p1.x, p1.y); b.lineTo(p3.x, p3.y); 187 b.lineTo(p4.x, p4.y); b.lineTo(p2.x, p2.y); 188 b.lineTo(p1.x, p1.y); 189 g2.draw(b); 190 g2.setStroke(new BasicStroke(1)); 191 } 192 } 193 194 /** 235 if (mode == Mode.select) { 236 // Nothing to do 237 } else { 238 if (newN1en != null) { 239 Graphics2D g2 = (Graphics2D)g; 240 g2.setColor(selectedColor); 241 g2.setStroke(new BasicStroke(3, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); 242 243 Point p1 = mv.getPoint(initialN1en); 244 Point p2 = mv.getPoint(initialN2en); 245 Point p3 = mv.getPoint(newN1en); 246 Point p4 = mv.getPoint(newN2en); 247 248 if (mode == Mode.extrude) { 249 // Draw rectangle around new area. 250 GeneralPath b = new GeneralPath(); 251 b.moveTo(p1.x, p1.y); b.lineTo(p3.x, p3.y); 252 b.lineTo(p4.x, p4.y); b.lineTo(p2.x, p2.y); 253 b.lineTo(p1.x, p1.y); 254 g2.draw(b); 255 g2.setStroke(new BasicStroke(1)); 256 } else if (mode == Mode.translate) { 257 // Highlight the new and old segments. 258 Line2D newline = new Line2D.Double(p3, p4); 259 g2.draw(newline); 260 g2.setStroke(new BasicStroke(1)); 261 Line2D oldline = new Line2D.Double(p1, p2); 262 g2.draw(oldline); 263 264 // Draw a guideline along the normal. 265 Line2D normline; 266 Point2D centerpoint = new Point2D.Double((p1.getX()+p2.getX())*0.5, (p1.getY()+p2.getY())*0.5); 267 EastNorth drawnorm; 268 // Check to see if our new N1 is in a positive direction with respect to the normalUnitVector. 269 // Even if the x component is zero, we should still be able to discern using +0.0 and -0.0 270 if (newN1en == null || (newN1en.getX() > initialN1en.getX() == normalUnitVector.getX() > -0.0)) 271 drawnorm = normalUnitVector; 272 else 273 // If not, use a sign-flipped version of the normalUnitVector. 274 drawnorm = new EastNorth(-normalUnitVector.getX(), -normalUnitVector.getY()); 275 normline = createSemiInfiniteLine(centerpoint, drawnorm, g2); 276 g2.draw(normline); 277 278 // EastNorth units per pixel 279 double factor = 1.0/g2.getTransform().getScaleX(); 280 281 // Draw right angle marker on initial position. 282 double raoffsetx = 8.0*factor*drawnorm.getX(); 283 double raoffsety = 8.0*factor*drawnorm.getY(); 284 Point2D ra1 = new Point2D.Double(centerpoint.getX()+raoffsetx, centerpoint.getY()+raoffsety); 285 Point2D ra3 = new Point2D.Double(centerpoint.getX()-raoffsety, centerpoint.getY()+raoffsetx); 286 Point2D ra2 = new Point2D.Double(ra1.getX()-raoffsety, ra1.getY()+raoffsetx); 287 GeneralPath ra = new GeneralPath(); 288 ra.moveTo(ra1.getX(), ra1.getY()); 289 ra.lineTo(ra2.getX(), ra2.getY()); 290 ra.lineTo(ra3.getX(), ra3.getY()); 291 g2.draw(ra); 292 } 293 } 294 } 295 } 296 297 /** 298 * If the left mouse button is pressed over a segment, switch 299 * to either extrude or translate mode depending on whether ctrl is held. 195 300 */ 196 301 @Override public void mousePressed(MouseEvent e) { … … 204 309 // boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0; 205 310 206 mouseDownTime = System.currentTimeMillis(); 207 208 selectedSegment = 209 Main.map.mapView.getNearestWaySegment(e.getPoint()); 210 211 mode = (selectedSegment == null) ? Mode.select : Mode.EXTRUDE; 212 oldCursor = Main.map.mapView.getCursor(); 213 214 updateStatusLine(); 215 Main.map.mapView.addTemporaryLayer(this); 216 Main.map.mapView.repaint(); 217 218 mousePos = e.getPoint(); 219 initialMousePos = e.getPoint(); 220 221 if(selectedSegment != null) { 222 getCurrentDataSet().setSelected(selectedSegment.way); 223 } 224 } 225 226 /** 227 * Restore the old mouse cursor. 311 selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint()); 312 313 if (selectedSegment == null) { 314 // If nothing gets caught, stay in select mode 315 } else { 316 // Otherwise switch to another mode 317 318 // For extrusion, these positions are actually never changed, 319 // but keeping note of this anyway allows us to not continually 320 // look it up and also allows us to unify code with the translate mode 321 initialN1en = selectedSegment.way.getNode(selectedSegment.lowerIndex).getEastNorth(); 322 initialN2en = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1).getEastNorth(); 323 324 // Signifies that nothing has happened yet 325 newN1en = null; 326 newN2en = null; 327 328 Main.map.mapView.addTemporaryLayer(this); 329 330 updateStatusLine(); 331 Main.map.mapView.repaint(); 332 333 // Make note of time pressed 334 mouseDownTime = System.currentTimeMillis(); 335 336 // Make note of mouse position 337 initialMousePos = e.getPoint(); 338 339 segmentVector = new EastNorth(initialN2en.getX()-initialN1en.getX(), initialN2en.getY()-initialN1en.getY()); 340 double factor = 1.0 / Math.hypot(segmentVector.getX(), segmentVector.getY()); 341 // swap coords to get normal, mult by factor to get unit vector. 342 normalUnitVector = new EastNorth(segmentVector.getY() * factor, segmentVector.getX() * factor); 343 344 // The calculation of points along the normal of the segment from mouse 345 // points is actually a purely affine mapping. So the majority of the maths 346 // can be done once, on mousePress, by building an AffineTransform which 347 // we can use in the other functions. 348 double r = 1.0 / ( (normalUnitVector.getX()*normalUnitVector.getX()) + (normalUnitVector.getY()*normalUnitVector.getY()) ); 349 double s = (normalUnitVector.getX()*initialN1en.getX()) - (normalUnitVector.getY()*initialN1en.getY()); 350 double compcoordcoeff = -r*normalUnitVector.getX()*normalUnitVector.getY(); 351 352 // Build the matrix. Takes a mouse position in EastNorth-space and returns the new position of node1 353 // based on that. 354 normalTransform = new AffineTransform( 355 r*normalUnitVector.getX()*normalUnitVector.getX(), compcoordcoeff, 356 compcoordcoeff, r*normalUnitVector.getY()*normalUnitVector.getY(), 357 initialN1en.getX()-(s*r*normalUnitVector.getX()), initialN1en.getY()+(s*r*normalUnitVector.getY())); 358 359 // Switch mode. 360 if ( (e.getModifiers() & ActionEvent.CTRL_MASK) != 0 ) { 361 mode = Mode.translate; 362 lastTranslatedN1en = initialN1en; 363 } else { 364 mode = Mode.extrude; 365 getCurrentDataSet().setSelected(selectedSegment.way); 366 } 367 } 368 } 369 370 /** 371 * Do anything that needs to be done, then switch back to select mode 228 372 */ 229 373 @Override public void mouseReleased(MouseEvent e) { 374 230 375 if(!Main.map.mapView.isActiveLayerVisible()) 231 376 return; 232 restoreCursor(); 233 if (selectedSegment == null) return; 234 if (mousePos.distance(initialMousePos) > 10) { 235 Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex); 236 Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1); 237 EastNorth en3 = n2.getEastNorth().add(xoff, yoff); 238 Node n3 = new Node(Main.proj.eastNorth2latlon(en3)); 239 EastNorth en4 = n1.getEastNorth().add(xoff, yoff); 240 Node n4 = new Node(Main.proj.eastNorth2latlon(en4)); 241 Way wnew = new Way(selectedSegment.way); 242 wnew.addNode(selectedSegment.lowerIndex+1, n3); 243 wnew.addNode(selectedSegment.lowerIndex+1, n4); 244 if (wnew.getNodesCount() == 4) { 245 wnew.addNode(n1); 377 378 if (mode == mode.select) { 379 // Nothing to be done 380 } else { 381 if (mode == mode.extrude) { 382 if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null) { 383 // Commit extrusion 384 385 Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex); 386 Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1); 387 Node n3 = new Node(Main.proj.eastNorth2latlon(newN2en)); 388 Node n4 = new Node(Main.proj.eastNorth2latlon(newN1en)); 389 Way wnew = new Way(selectedSegment.way); 390 wnew.addNode(selectedSegment.lowerIndex+1, n3); 391 wnew.addNode(selectedSegment.lowerIndex+1, n4); 392 if (wnew.getNodesCount() == 4) { 393 wnew.addNode(n1); 394 } 395 Collection<Command> cmds = new LinkedList<Command>(); 396 cmds.add(new AddCommand(n4)); 397 cmds.add(new AddCommand(n3)); 398 cmds.add(new ChangeCommand(selectedSegment.way, wnew)); 399 Command c = new SequenceCommand(tr("Extrude Way"), cmds); 400 Main.main.undoRedo.add(c); 401 } 402 } else if (mode == mode.translate) { 403 // I don't think there's anything to do 246 404 } 247 Collection<Command> cmds = new LinkedList<Command>(); 248 cmds.add(new AddCommand(n4)); 249 cmds.add(new AddCommand(n3)); 250 cmds.add(new ChangeCommand(selectedSegment.way, wnew)); 251 Command c = new SequenceCommand(tr("Extrude Way"), cmds); 252 Main.main.undoRedo.add(c); 253 } 254 255 Main.map.mapView.removeTemporaryLayer(this); 256 selectedSegment = null; 257 mode = null; 258 updateStatusLine(); 259 Main.map.mapView.repaint(); 405 406 // Switch back into select mode 407 restoreCursor(); 408 Main.map.mapView.removeTemporaryLayer(this); 409 selectedSegment = null; 410 mode = Mode.select; 411 412 updateStatusLine(); 413 Main.map.mapView.repaint(); 414 } 260 415 } 261 416 262 417 @Override public String getModeHelpText() { 263 if (mode == Mode. select)264 return tr(" Release the mouse button to select the objects in the rectangle.");265 else if (mode == Mode. EXTRUDE)418 if (mode == Mode.translate) 419 return tr("Move a segment along its normal, then release the mouse button."); 420 else if (mode == Mode.extrude) 266 421 return tr("Draw a rectangle of the desired size, then release the mouse button."); 267 else if (mode == Mode.rotate)268 return tr("Release the mouse button to stop rotating.");269 422 else 270 return tr("Drag a way segment to make a rectangle. ");423 return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal."); 271 424 } 272 425
Note:
See TracChangeset
for help on using the changeset viewer.