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; |
138 | | if (mode == Mode.EXTRUDE) { |
| 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 | |
142 | | if (mousePos == null) { |
143 | | mousePos = e.getPoint(); |
144 | | return; |
| 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(); |
147 | | Main.map.mapView.repaint(); |
148 | | mousePos = e.getPoint(); |
| 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); |
| 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 | } |
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())); |
| 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); |
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(); |
| 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); |
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)); |
| 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 | } |
211 | | mode = (selectedSegment == null) ? Mode.select : Mode.EXTRUDE; |
212 | | oldCursor = Main.map.mapView.getCursor(); |
| 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(); |
221 | | if(selectedSegment != null) { |
222 | | getCurrentDataSet().setSelected(selectedSegment.way); |
| 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 | } |
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 |
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); |
| 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(); |