| | 141 | * If the left mouse button is pressed over a segment, switch |
| | 142 | * to either extrude or translate mode depending on whether ctrl is held. |
| | 143 | */ |
| | 144 | @Override public void mousePressed(MouseEvent e) { |
| | 145 | if(!Main.map.mapView.isActiveLayerVisible()) |
| | 146 | return; |
| | 147 | if (!(Boolean)this.getValue("active")) |
| | 148 | return; |
| | 149 | if (e.getButton() != MouseEvent.BUTTON1) |
| | 150 | return; |
| | 151 | |
| | 152 | selectedSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate); |
| | 153 | |
| | 154 | if (selectedSegment == null) { |
| | 155 | // If nothing gets caught, stay in select mode |
| | 156 | } else { |
| | 157 | // Otherwise switch to another mode |
| | 158 | |
| | 159 | if ( (e.getModifiers() & ActionEvent.CTRL_MASK) != 0 ) { |
| | 160 | mode = Mode.translate; |
| | 161 | } else { |
| | 162 | mode = Mode.extrude; |
| | 163 | getCurrentDataSet().setSelected(selectedSegment.way); |
| | 164 | } |
| | 165 | |
| | 166 | |
| | 167 | // remember initial positions for segment nodes. |
| | 168 | initialN1en = selectedSegment.getFirstNode().getEastNorth(); |
| | 169 | initialN2en = selectedSegment.getSecondNode().getEastNorth(); |
| | 170 | |
| | 171 | //gather possible move directions - perpendicular to the selected segment and parallel to neighbor segments |
| | 172 | possibleMoveDirections = new ArrayList<EastNorth>(); |
| | 173 | possibleMoveDirections.add(new EastNorth( |
| | 174 | initialN1en.getY() - initialN2en.getY(), |
| | 175 | initialN2en.getX() - initialN1en.getX())); |
| | 176 | |
| | 177 | |
| | 178 | if (mode == Mode.extrude) |
| | 179 | { |
| | 180 | //add directions parralel to neighbour segments |
| | 181 | |
| | 182 | Node prevNode = getPreviousNode(selectedSegment.lowerIndex); |
| | 183 | if (prevNode != null) |
| | 184 | { |
| | 185 | EastNorth en = prevNode.getEastNorth(); |
| | 186 | possibleMoveDirections.add(new EastNorth( |
| | 187 | initialN1en.getX() - en.getX(), |
| | 188 | initialN1en.getY() - en.getY())); |
| | 189 | } |
| | 190 | |
| | 191 | Node nextNode = getNextNode(selectedSegment.lowerIndex + 1); |
| | 192 | if (nextNode != null) |
| | 193 | { |
| | 194 | EastNorth en = nextNode.getEastNorth(); |
| | 195 | possibleMoveDirections.add(new EastNorth( |
| | 196 | initialN2en.getX() - en.getX(), |
| | 197 | initialN2en.getY() - en.getY())); |
| | 198 | } |
| | 199 | } |
| | 200 | |
| | 201 | // Signifies that nothing has happened yet |
| | 202 | newN1en = null; |
| | 203 | newN2en = null; |
| | 204 | moveCommand = null; |
| | 205 | |
| | 206 | Main.map.mapView.addTemporaryLayer(this); |
| | 207 | |
| | 208 | updateStatusLine(); |
| | 209 | Main.map.mapView.repaint(); |
| | 210 | |
| | 211 | // Make note of time pressed |
| | 212 | mouseDownTime = System.currentTimeMillis(); |
| | 213 | |
| | 214 | // Make note of mouse position |
| | 215 | initialMousePos = e.getPoint(); |
| | 216 | } |
| | 217 | } |
| | 218 | |
| | 219 | |
| | 220 | /** |
| 179 | | Command c = !Main.main.undoRedo.commands.isEmpty() |
| 180 | | ? Main.main.undoRedo.commands.getLast() : null; |
| 181 | | if (c instanceof SequenceCommand) { |
| 182 | | c = ((SequenceCommand)c).getLastCommand(); |
| | 273 | //move nodes to new position |
| | 274 | if (moveCommand == null) |
| | 275 | { |
| | 276 | //make a new move command |
| | 277 | Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>(); |
| | 278 | nodelist.add(selectedSegment.getFirstNode()); |
| | 279 | nodelist.add(selectedSegment.getSecondNode()); |
| | 280 | moveCommand = new MoveCommand(nodelist, bestMovement.getX(), bestMovement.getY()); |
| | 281 | Main.main.undoRedo.add(moveCommand); |
| | 282 | } else { |
| | 283 | //reuse existing move command |
| | 284 | moveCommand.undoCommand(); |
| | 285 | moveCommand.moveAgain(bestMovement.getX(), bestMovement.getY()); |
| 190 | | // Better way of testing list equality non-order-sensitively? |
| 191 | | if (c instanceof MoveCommand |
| 192 | | && ((MoveCommand)c).getParticipatingPrimitives().contains(n1) |
| 193 | | && ((MoveCommand)c).getParticipatingPrimitives().contains(n2) |
| 194 | | && ((MoveCommand)c).getParticipatingPrimitives().size() == 2) { |
| 195 | | // MoveCommand doesn't let us know how much it has already moved the selection |
| 196 | | // so we have to do some ugly record-keeping. |
| 197 | | ((MoveCommand)c).moveAgain(difference.getX(), difference.getY()); |
| 198 | | lastTranslatedN1en = newN1en; |
| 199 | | } else { |
| 200 | | Collection<OsmPrimitive> nodelist = new LinkedList<OsmPrimitive>(); |
| 201 | | nodelist.add(n1); |
| 202 | | nodelist.add(n2); |
| 203 | | Main.main.undoRedo.add(c = new MoveCommand(nodelist, difference.getX(), difference.getY())); |
| 204 | | lastTranslatedN1en = newN1en; |
| | 294 | /** |
| | 295 | * Do anything that needs to be done, then switch back to select mode |
| | 296 | */ |
| | 297 | @Override public void mouseReleased(MouseEvent e) { |
| | 298 | |
| | 299 | if(!Main.map.mapView.isActiveLayerVisible()) |
| | 300 | return; |
| | 301 | |
| | 302 | if (mode == Mode.select) { |
| | 303 | // Nothing to be done |
| | 304 | } else { |
| | 305 | if (mode == Mode.extrude) { |
| | 306 | if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null) { |
| | 307 | // create extrusion |
| | 308 | |
| | 309 | Collection<Command> cmds = new LinkedList<Command>(); |
| | 310 | Way wnew = new Way(selectedSegment.way); |
| | 311 | int insertionPoint = selectedSegment.lowerIndex + 1; |
| | 312 | |
| | 313 | //find if the new points overlap existing segments (in case of 90 degree angles) |
| | 314 | Node prevNode = getPreviousNode(selectedSegment.lowerIndex); |
| | 315 | boolean nodeOverlapsSegment = prevNode != null && pointsColinear(prevNode.getEastNorth(), initialN1en, newN1en); |
| | 316 | |
| | 317 | if (nodeOverlapsSegment){ |
| | 318 | //move existing node |
| | 319 | Node n1Old = selectedSegment.getFirstNode(); |
| | 320 | cmds.add(new MoveCommand(n1Old, Main.proj.eastNorth2latlon(newN1en))); |
| | 321 | } else{ |
| | 322 | //introduce new node |
| | 323 | Node n1New = new Node(Main.proj.eastNorth2latlon(newN1en)); |
| | 324 | wnew.addNode(insertionPoint, n1New); |
| | 325 | insertionPoint ++; |
| | 326 | cmds.add(new AddCommand(n1New)); |
| | 327 | } |
| | 328 | |
| | 329 | //find if the new points overlap existing segments (in case of 90 degree angles) |
| | 330 | Node nextNode = getNextNode(selectedSegment.lowerIndex + 1); |
| | 331 | nodeOverlapsSegment = nextNode != null && pointsColinear(nextNode.getEastNorth(), initialN2en, newN2en); |
| | 332 | |
| | 333 | if (nodeOverlapsSegment){ |
| | 334 | //move existing node |
| | 335 | Node n2Old = selectedSegment.getSecondNode(); |
| | 336 | cmds.add(new MoveCommand(n2Old, Main.proj.eastNorth2latlon(newN2en))); |
| | 337 | } else{ |
| | 338 | //introduce new node |
| | 339 | Node n2New = new Node(Main.proj.eastNorth2latlon(newN2en)); |
| | 340 | wnew.addNode(insertionPoint, n2New); |
| | 341 | insertionPoint ++; |
| | 342 | cmds.add(new AddCommand(n2New)); |
| | 343 | } |
| | 344 | |
| | 345 | //the way was a single segment, close the way |
| | 346 | if (wnew.getNodesCount() == 4) { |
| | 347 | wnew.addNode(selectedSegment.getFirstNode()); |
| | 348 | } |
| | 349 | |
| | 350 | cmds.add(new ChangeCommand(selectedSegment.way, wnew)); |
| | 351 | Command c = new SequenceCommand(tr("Extrude Way"), cmds); |
| | 352 | Main.main.undoRedo.add(c); |
| | 371 | /*** |
| | 372 | * This method calculates offset amount by witch to move the given segment perpendicularly for it to be in line with mouse position. |
| | 373 | * @param segmentP1 |
| | 374 | * @param segmentP2 |
| | 375 | * @param targetPos |
| | 376 | * @return offset amount of P1 and P2. |
| | 377 | */ |
| | 378 | private static EastNorth calculateSegmentOffset(EastNorth segmentP1, EastNorth segmentP2 ,EastNorth moveDirection , EastNorth targetPos) |
| | 379 | { |
| | 380 | EastNorth intersectionPoint = getLineLineIntersection( |
| | 381 | segmentP1, |
| | 382 | segmentP2, |
| | 383 | targetPos, |
| | 384 | new EastNorth(targetPos.getX() + moveDirection.getX(), targetPos.getY() + moveDirection.getY())); |
| | 385 | |
| | 386 | if (intersectionPoint == null) |
| | 387 | return null; |
| | 388 | else |
| | 389 | //return distance form base to target position |
| | 390 | return new EastNorth(targetPos.getX() - intersectionPoint.getX(), targetPos.getY() - intersectionPoint.getY()); |
| | 391 | } |
| | 392 | |
| 217 | | static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) { |
| 218 | | Rectangle bounds = g.getDeviceConfiguration().getBounds(); |
| 219 | | try { |
| 220 | | AffineTransform invtrans = g.getTransform().createInverse(); |
| 221 | | Point2D widthpoint = invtrans.deltaTransform(new Point2D.Double(bounds.width,0), null); |
| 222 | | Point2D heightpoint = invtrans.deltaTransform(new Point2D.Double(0,bounds.height), null); |
| | 397 | static public EastNorth getLineLineIntersection( |
| | 398 | EastNorth p1, EastNorth p2, |
| | 399 | EastNorth p3, EastNorth p4) { |
| 229 | | return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * linelength) , start.getY() + (unitvector.getY() * linelength))); |
| | 406 | double a2 = p4.getY() - p3.getY(); |
| | 407 | double b2 = p3.getX() - p4.getX(); |
| | 408 | double c2 = p4.getX() * p3.getY() - p3.getX() * p4.getY(); |
| | 409 | |
| | 410 | // Solve the equations |
| | 411 | double det = a1*b2 - a2*b1; |
| | 412 | if(det == 0) return null; // Lines are parallel |
| | 413 | |
| | 414 | return new EastNorth( |
| | 415 | (b1*c2 - b2*c1)/det, |
| | 416 | (a2*c1 - a1*c2)/det); |
| | 417 | } |
| | 418 | |
| | 419 | |
| | 420 | /** |
| | 421 | * Returns true if all points are on the same line. |
| | 422 | */ |
| | 423 | private static boolean pointsColinear(EastNorth p1, EastNorth p2, EastNorth p3){ |
| | 424 | |
| | 425 | //the simple dumb way of triangle side lengths. |
| | 426 | double distance1 = p1.distance(p2); |
| | 427 | double distance2 = p1.distance(p3); |
| | 428 | double distance3 = p2.distance(p3); |
| | 429 | |
| | 430 | //sort so that distance 1 is the greatest |
| | 431 | if (distance1 < distance2){ |
| | 432 | double temp = distance1; |
| | 433 | distance1 = distance2; |
| | 434 | distance2 = temp; |
| | 450 | /** |
| | 451 | * Gets a node from selected way before given index. |
| | 452 | * @param index index of current node |
| | 453 | * @return previous node or null if there are no nodes there. |
| | 454 | */ |
| | 455 | private Node getPreviousNode(int index){ |
| | 456 | if (index > 0) |
| | 457 | return selectedSegment.way.getNode(index - 1); |
| | 458 | else if (selectedSegment.way.isClosed()) |
| | 459 | return selectedSegment.way.getNode(selectedSegment.way.getNodesCount() - 2); |
| | 460 | else |
| | 461 | return null; |
| | 462 | } |
| | 463 | |
| | 464 | /** |
| | 465 | * Gets a node from selected way before given index. |
| | 466 | * @param index index of current node |
| | 467 | * @return next node or null if there are no nodes there. |
| | 468 | */ |
| | 469 | private Node getNextNode(int index){ |
| | 470 | int count = selectedSegment.way.getNodesCount(); |
| | 471 | if (index < count - 1) |
| | 472 | return selectedSegment.way.getNode(index + 1); |
| | 473 | else if (selectedSegment.way.isClosed()) |
| | 474 | return selectedSegment.way.getNode(1); |
| | 475 | else |
| | 476 | return null; |
| | 477 | } |
| | 478 | |
| 310 | | @Override public void mousePressed(MouseEvent e) { |
| 311 | | if(!Main.map.mapView.isActiveLayerVisible()) |
| 312 | | return; |
| 313 | | if (!(Boolean)this.getValue("active")) return; |
| 314 | | if (e.getButton() != MouseEvent.BUTTON1) |
| 315 | | return; |
| 316 | | // boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0; |
| 317 | | // boolean alt = (e.getModifiers() & ActionEvent.ALT_MASK) != 0; |
| 318 | | // boolean shift = (e.getModifiers() & ActionEvent.SHIFT_MASK) != 0; |
| | 557 | static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) { |
| | 558 | Rectangle bounds = g.getDeviceConfiguration().getBounds(); |
| | 559 | try { |
| | 560 | AffineTransform invtrans = g.getTransform().createInverse(); |
| | 561 | Point2D widthpoint = invtrans.deltaTransform(new Point2D.Double(bounds.width,0), null); |
| | 562 | Point2D heightpoint = invtrans.deltaTransform(new Point2D.Double(0,bounds.height), null); |
| 322 | | if (selectedSegment == null) { |
| 323 | | // If nothing gets caught, stay in select mode |
| 324 | | } else { |
| 325 | | // Otherwise switch to another mode |
| 326 | | |
| 327 | | // For extrusion, these positions are actually never changed, |
| 328 | | // but keeping note of this anyway allows us to not continually |
| 329 | | // look it up and also allows us to unify code with the translate mode |
| 330 | | initialN1en = selectedSegment.way.getNode(selectedSegment.lowerIndex).getEastNorth(); |
| 331 | | initialN2en = selectedSegment.way.getNode(selectedSegment.lowerIndex + 1).getEastNorth(); |
| 332 | | |
| 333 | | // Signifies that nothing has happened yet |
| 334 | | newN1en = null; |
| 335 | | newN2en = null; |
| 336 | | |
| 337 | | Main.map.mapView.addTemporaryLayer(this); |
| 338 | | |
| 339 | | updateStatusLine(); |
| 340 | | Main.map.mapView.repaint(); |
| 341 | | |
| 342 | | // Make note of time pressed |
| 343 | | mouseDownTime = System.currentTimeMillis(); |
| 344 | | |
| 345 | | // Make note of mouse position |
| 346 | | initialMousePos = e.getPoint(); |
| 347 | | |
| 348 | | // Switch mode. |
| 349 | | if ( (e.getModifiers() & ActionEvent.CTRL_MASK) != 0 ) { |
| 350 | | mode = Mode.translate; |
| 351 | | lastTranslatedN1en = initialN1en; |
| 352 | | } else { |
| 353 | | mode = Mode.extrude; |
| 354 | | getCurrentDataSet().setSelected(selectedSegment.way); |
| 355 | | } |
| | 569 | return new Line2D.Double(start, new Point2D.Double(start.getX() + (unitvector.getX() * linelength) , start.getY() + (unitvector.getY() * linelength))); |
| 359 | | /** |
| 360 | | * Do anything that needs to be done, then switch back to select mode |
| 361 | | */ |
| 362 | | @Override public void mouseReleased(MouseEvent e) { |
| 363 | | |
| 364 | | if(!Main.map.mapView.isActiveLayerVisible()) |
| 365 | | return; |
| 366 | | |
| 367 | | if (mode == Mode.select) { |
| 368 | | // Nothing to be done |
| 369 | | } else { |
| 370 | | if (mode == Mode.extrude) { |
| 371 | | if (e.getPoint().distance(initialMousePos) > 10 && newN1en != null) { |
| 372 | | // Commit extrusion |
| 373 | | |
| 374 | | Node n1 = selectedSegment.way.getNode(selectedSegment.lowerIndex); |
| 375 | | //Node n2 = selectedSegment.way.getNode(selectedSegment.lowerIndex+1); |
| 376 | | Node n3 = new Node(Main.proj.eastNorth2latlon(newN2en)); |
| 377 | | Node n4 = new Node(Main.proj.eastNorth2latlon(newN1en)); |
| 378 | | Way wnew = new Way(selectedSegment.way); |
| 379 | | wnew.addNode(selectedSegment.lowerIndex+1, n3); |
| 380 | | wnew.addNode(selectedSegment.lowerIndex+1, n4); |
| 381 | | if (wnew.getNodesCount() == 4) { |
| 382 | | wnew.addNode(n1); |
| 383 | | } |
| 384 | | Collection<Command> cmds = new LinkedList<Command>(); |
| 385 | | cmds.add(new AddCommand(n4)); |
| 386 | | cmds.add(new AddCommand(n3)); |
| 387 | | cmds.add(new ChangeCommand(selectedSegment.way, wnew)); |
| 388 | | Command c = new SequenceCommand(tr("Extrude Way"), cmds); |
| 389 | | Main.main.undoRedo.add(c); |
| 390 | | } |
| 391 | | } else if (mode == Mode.translate) { |
| 392 | | // I don't think there's anything to do |
| 393 | | } |
| 394 | | |
| 395 | | // Switch back into select mode |
| 396 | | restoreCursor(); |
| 397 | | Main.map.mapView.removeTemporaryLayer(this); |
| 398 | | selectedSegment = null; |
| 399 | | mode = Mode.select; |
| 400 | | |
| 401 | | updateStatusLine(); |
| 402 | | Main.map.mapView.repaint(); |
| | 576 | private static Cursor getCursor(String name, String mod, int def) { |
| | 577 | try { |
| | 578 | return ImageProvider.getCursor(name, mod); |
| | 579 | } catch (Exception e) { |