Ticket #7991: 7991-DualAlign.r6252.patch
File 7991-DualAlign.r6252.patch, 34.4 KB (added by , 11 years ago) |
---|
-
src/org/openstreetmap/josm/actions/mapmode/ExtrudeAction.java
8 8 import java.awt.AWTEvent; 9 9 import java.awt.BasicStroke; 10 10 import java.awt.Color; 11 import java.awt.Component; 11 12 import java.awt.Cursor; 12 13 import java.awt.Graphics2D; 14 import java.awt.KeyboardFocusManager; 13 15 import java.awt.Point; 14 16 import java.awt.Rectangle; 15 17 import java.awt.Stroke; … … 16 18 import java.awt.Toolkit; 17 19 import java.awt.event.AWTEventListener; 18 20 import java.awt.event.ActionEvent; 21 import java.awt.event.ActionListener; 19 22 import java.awt.event.InputEvent; 20 23 import java.awt.event.KeyEvent; 21 24 import java.awt.event.MouseEvent; … … 28 31 import java.util.Collection; 29 32 import java.util.LinkedList; 30 33 import java.util.List; 34 import java.util.TreeSet; 35 import javax.swing.JCheckBoxMenuItem; 36 import javax.swing.JFrame; 37 import javax.swing.JMenuItem; 38 import javax.swing.SwingUtilities; 39 import javax.swing.Timer; 31 40 32 41 import org.openstreetmap.josm.Main; 42 import org.openstreetmap.josm.actions.JosmAction; 33 43 import org.openstreetmap.josm.command.AddCommand; 34 44 import org.openstreetmap.josm.command.ChangeCommand; 35 45 import org.openstreetmap.josm.command.Command; … … 42 52 import org.openstreetmap.josm.data.osm.Way; 43 53 import org.openstreetmap.josm.data.osm.WaySegment; 44 54 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors; 55 import org.openstreetmap.josm.gui.MainMenu; 45 56 import org.openstreetmap.josm.gui.MapFrame; 46 57 import org.openstreetmap.josm.gui.MapView; 47 58 import org.openstreetmap.josm.gui.layer.Layer; … … 62 73 private Mode mode = Mode.select; 63 74 64 75 /** 65 * If true, when extruding create new node even if segmentsparallel.76 * If {@code true}, when extruding create new node(s) even if segments are parallel. 66 77 */ 67 78 private boolean alwaysCreateNodes = false; 68 79 private boolean nodeDragWithoutCtrl; … … 94 105 /** 95 106 * Collection of nodes that is moved 96 107 */ 97 private Collection<OsmPrimitive> movingNodeList;108 private ArrayList<OsmPrimitive> movingNodeList; 98 109 99 110 /** 100 111 * The direction that is currently active. … … 125 136 * the command that performed last move. 126 137 */ 127 138 private MoveCommand moveCommand; 139 /** 140 * The command used for dual alignment movement. 141 * Needs to be separate, due to two nodes moving in different directions. 142 */ 143 private MoveCommand moveCommand2; 128 144 129 145 /** The cursor for the 'create_new' mode. */ 130 146 private final Cursor cursorCreateNew; … … 135 151 /** The cursor for the 'alwaysCreateNodes' submode. */ 136 152 private final Cursor cursorCreateNodes; 137 153 138 private class ReferenceSegment {154 private static class ReferenceSegment { 139 155 public final EastNorth en; 140 156 public final EastNorth p1; 141 157 public final EastNorth p2; … … 147 163 this.p2 = p2; 148 164 this.perpendicular = perpendicular; 149 165 } 166 167 @Override 168 public String toString() { 169 return "ReferenceSegment[en=" + en + ", p1=" + p1 + ", p2=" + p2 + ", perp=" + perpendicular + "]"; 170 } 150 171 } 151 172 152 /** 153 * This listener is used to indicate the 'create_new' mode, if the Alt modifier is pressed. 154 */ 155 private final AWTEventListener altKeyListener = new AWTEventListener() { 173 // Dual alignment mode stuff 174 /** {@code true}, if dual alignment mode is enabled. User wants following extrude to be dual aligned. */ 175 private boolean dualAlignEnabled; 176 /** {@code true}, if dual alignment is active. User is dragging the mouse, required conditions are met. Treat {@link #mode} (extrude/translate/create_new) as dual aligned. */ 177 private boolean dualAlignActive; 178 /** Dual alignment reference segments */ 179 private ReferenceSegment dualAlignSegment1, dualAlignSegment2; 180 // Dual alignment UI stuff 181 private final DualAlignChangeAction dualAlignChangeAction; 182 private final JCheckBoxMenuItem dualAlignCheckboxMenuItem; 183 private final Shortcut dualAlignShortcut; 184 private boolean useRepeatedShortcut; 185 186 private class DualAlignChangeAction extends JosmAction { 187 public DualAlignChangeAction() { 188 super(tr("Dual alignment"), "mapmode/extrude/dualalign", 189 tr("Switch dual alignment mode while extruding"), null, false); 190 putValue("help", ht("/Action/Extrude/DualAlign")); 191 } 192 156 193 @Override 157 public void eventDispatched(AWTEvent e) { 158 if(Main.map == null || Main.map.mapView == null || !Main.map.mapView.isActiveLayerDrawable()) 159 return; 160 InputEvent ie = (InputEvent) e; 161 boolean alt = (ie.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0; 162 boolean ctrl = (ie.getModifiers() & (ActionEvent.CTRL_MASK)) != 0; 163 boolean shift = (ie.getModifiers() & (ActionEvent.SHIFT_MASK)) != 0; 164 if (mode == Mode.select) { 165 Main.map.mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this); 166 } 194 public void actionPerformed(ActionEvent e) { 195 toggleDualAlign(); 167 196 } 168 } ;197 } 169 198 170 199 /** 171 * Create a new SelectAction200 * Creates a new ExtrudeAction 172 201 * @param mapFrame The MapFrame this action belongs to. 173 202 */ 174 203 public ExtrudeAction(MapFrame mapFrame) { … … 180 209 cursorCreateNew = ImageProvider.getCursor("normal", "rectangle_plus"); 181 210 cursorTranslate = ImageProvider.getCursor("normal", "rectangle_move"); 182 211 cursorCreateNodes = ImageProvider.getCursor("normal", "rectangle_plussmall"); 212 213 dualAlignEnabled = false; 214 dualAlignChangeAction = new DualAlignChangeAction(); 215 dualAlignCheckboxMenuItem = addDualAlignMenuItem(); 216 dualAlignCheckboxMenuItem.getAction().setEnabled(false); 217 dualAlignCheckboxMenuItem.setState(dualAlignEnabled); 218 dualAlignShortcut = Shortcut.registerShortcut("mapmode:extrudedualalign", 219 tr("Mode: {0}", tr("Extrude Dual alignment")), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE); 220 useRepeatedShortcut = Main.pref.getBoolean("extrude.dualalign.toggleOnRepeatedX", true); 221 timer = new Timer(0, new ActionListener() { 222 @Override 223 public void actionPerformed(ActionEvent ae) { 224 timer.stop(); 225 if (set.remove(releaseEvent.getKeyCode())) { 226 doKeyReleaseEvent(releaseEvent); 227 } 228 } 229 }); 183 230 } 184 231 185 @Override public String getModeHelpText() { 186 if (mode == Mode.translate) 187 return tr("Move a segment along its normal, then release the mouse button."); 188 else if (mode == Mode.extrude) 189 return tr("Draw a rectangle of the desired size, then release the mouse button."); 190 else if (mode == Mode.create_new) 191 return tr("Draw a rectangle of the desired size, then release the mouse button."); 192 else 193 return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal, " + 194 "Alt-drag to create a new rectangle, double click to add a new node."); 232 @Override 233 public void destroy() { 234 super.destroy(); 235 dualAlignChangeAction.destroy(); 195 236 } 196 237 197 @Override public boolean layerIsSupported(Layer l) { 238 private JCheckBoxMenuItem addDualAlignMenuItem() { 239 int n = Main.main.menu.editMenu.getItemCount(); 240 for (int i = n-1; i>0; i--) { 241 JMenuItem item = Main.main.menu.editMenu.getItem(i); 242 if (item != null && item.getAction() != null && item.getAction() instanceof DualAlignChangeAction) { 243 Main.main.menu.editMenu.remove(i); 244 } 245 } 246 return MainMenu.addWithCheckbox(Main.main.menu.editMenu, dualAlignChangeAction, MainMenu.WINDOW_MENU_GROUP.VOLATILE); 247 } 248 249 // ------------------------------------------------------------------------- 250 // Mode methods 251 // ------------------------------------------------------------------------- 252 253 @Override 254 public String getModeHelpText() { 255 String rv; 256 if (mode == Mode.select) { 257 rv = tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal, " + 258 "Alt-drag to create a new rectangle, double click to add a new node."); 259 if (dualAlignEnabled) 260 rv += " " + tr("Dual alignment active."); 261 } else { 262 if (mode == Mode.translate) 263 rv = tr("Move a segment along its normal, then release the mouse button."); 264 else if (mode == Mode.translate_node) 265 rv = tr("Move the node along one of the segments, then release the mouse button."); 266 else if (mode == Mode.extrude) 267 rv = tr("Draw a rectangle of the desired size, then release the mouse button."); 268 else if (mode == Mode.create_new) 269 rv = tr("Draw a rectangle of the desired size, then release the mouse button."); 270 else { 271 Main.warn("Extrude: unknown mode " + mode); 272 rv = ""; 273 } 274 if (dualAlignActive) 275 rv += " " + tr("Dual alignment active."); 276 } 277 return rv; 278 } 279 280 @Override 281 public boolean layerIsSupported(Layer l) { 198 282 return l instanceof OsmDataLayer; 199 283 } 200 284 201 @Override public void enterMode() { 285 @Override 286 public void enterMode() { 202 287 super.enterMode(); 203 288 Main.map.mapView.addMouseListener(this); 204 289 Main.map.mapView.addMouseMotionListener(this); … … 218 303 mainStroke = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.stroke.main", "3")); 219 304 220 305 ignoreSharedNodes = Main.pref.getBoolean("extrude.ignore-shared-nodes", true); 306 dualAlignCheckboxMenuItem.getAction().setEnabled(true); 221 307 } 222 308 223 @Override public void exitMode() { 309 @Override 310 public void exitMode() { 224 311 Main.map.mapView.removeMouseListener(this); 225 312 Main.map.mapView.removeMouseMotionListener(this); 226 313 Main.map.mapView.removeTemporaryLayer(this); 314 dualAlignCheckboxMenuItem.getAction().setEnabled(false); 227 315 try { 228 316 Toolkit.getDefaultToolkit().removeAWTEventListener(altKeyListener); 229 317 } catch (SecurityException ex) { … … 231 319 super.exitMode(); 232 320 } 233 321 322 // ------------------------------------------------------------------------- 323 // Event handlers 324 // ------------------------------------------------------------------------- 325 234 326 /** 235 * If the left mouse button is pressed over a segment, switch236 * to either extrude, translate or create_new mode depending on whether Ctrl or Alt is held.327 * This listener is used to indicate different modes via cursor when the Alt/Ctrl/Shift modifier is pressed, 328 * and for listening to dual alignment shortcuts. 237 329 */ 238 @Override public void mousePressed(MouseEvent e) { 330 private final AWTEventListener altKeyListener = new AWTEventListener() { 331 @Override 332 public void eventDispatched(AWTEvent e) { 333 if(Main.map == null || Main.map.mapView == null || !Main.map.mapView.isActiveLayerDrawable()) 334 return; 335 InputEvent ie = (InputEvent) e; 336 boolean alt = (ie.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0; 337 boolean ctrl = (ie.getModifiers() & (ActionEvent.CTRL_MASK)) != 0; 338 boolean shift = (ie.getModifiers() & (ActionEvent.SHIFT_MASK)) != 0; 339 if (mode == Mode.select) { 340 Main.map.mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this); 341 } 342 if (e instanceof KeyEvent) { 343 KeyEvent ke = (KeyEvent) e; 344 if (dualAlignShortcut.isEvent(ke) || (useRepeatedShortcut && getShortcut().isEvent(ke))) { 345 Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 346 if (SwingUtilities.getWindowAncestor(focused) instanceof JFrame) { 347 processKeyEvent(ke); 348 } 349 } 350 } 351 } 352 }; 353 354 // events for crossplatform key holding processing 355 // thanks to http://www.arco.in-berlin.de/keyevent.html 356 private final TreeSet<Integer> set = new TreeSet<Integer>(); 357 private KeyEvent releaseEvent; 358 private Timer timer; 359 private void processKeyEvent(KeyEvent e) { 360 if (!dualAlignShortcut.isEvent(e) && !(useRepeatedShortcut && getShortcut().isEvent(e))) 361 return; 362 363 if (e.getID() == KeyEvent.KEY_PRESSED) { 364 if (timer.isRunning()) { 365 timer.stop(); 366 } else if (set.add((e.getKeyCode()))) { 367 doKeyPressEvent(e); 368 } 369 } else if (e.getID() == KeyEvent.KEY_RELEASED) { 370 if (timer.isRunning()) { 371 timer.stop(); 372 if (set.remove(e.getKeyCode())) { 373 doKeyReleaseEvent(e); 374 } 375 } else { 376 releaseEvent = e; 377 timer.restart(); 378 } 379 } 380 } 381 382 private void doKeyPressEvent(KeyEvent e) { 383 } 384 385 private void doKeyReleaseEvent(KeyEvent e) { 386 toggleDualAlign(); 387 } 388 389 /** 390 * Toggles dual alignment mode. 391 */ 392 private void toggleDualAlign() { 393 dualAlignEnabled = !dualAlignEnabled; 394 dualAlignCheckboxMenuItem.setState(dualAlignEnabled); 395 updateStatusLine(); 396 } 397 398 /** 399 * If the left mouse button is pressed over a segment or a node, switches 400 * to appropriate {@link #mode}, depending on Ctrl/Alt/Shift modifiers and 401 * {@link #dualAlignEnabled}. 402 * @param e 403 */ 404 @Override 405 public void mousePressed(MouseEvent e) { 239 406 if(!Main.map.mapView.isActiveLayerVisible()) 240 407 return; 241 408 if (!(Boolean)this.getValue("active")) … … 262 429 return; 263 430 } 264 431 mode = Mode.translate_node; 432 dualAlignActive = false; 265 433 } 266 434 } else { 267 435 // Otherwise switch to another mode 436 if (dualAlignEnabled && checkDualAlignConditions()) { 437 dualAlignActive = true; 438 calculatePossibleDirectionsForDualAlign(); 439 } else { 440 dualAlignActive = false; 441 calculatePossibleDirectionsBySegment(); 442 } 268 443 if (ctrl) { 269 444 mode = Mode.translate; 270 445 movingNodeList = new ArrayList<OsmPrimitive>(); … … 280 455 getCurrentDataSet().setSelected(selectedSegment.way); 281 456 alwaysCreateNodes = shift; 282 457 } 283 calculatePossibleDirectionsBySegment();284 458 } 285 459 286 460 // Signifies that nothing has happened yet … … 287 461 newN1en = null; 288 462 newN2en = null; 289 463 moveCommand = null; 464 moveCommand2 = null; 290 465 291 466 Main.map.mapView.addTemporaryLayer(this); 292 467 … … 301 476 } 302 477 303 478 /** 304 * Perform action depending on what mode we're in. 479 * Performs action depending on what {@link #mode} we're in. 480 * @param e 305 481 */ 306 @Override public void mouseDragged(MouseEvent e) { 482 @Override 483 public void mouseDragged(MouseEvent e) { 307 484 if(!Main.map.mapView.isActiveLayerVisible()) 308 485 return; 309 486 … … 318 495 319 496 EastNorth mouseEn = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y); 320 497 EastNorth bestMovement = calculateBestMovement(mouseEn); 498 EastNorth n1movedEn = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY()); 321 499 322 newN1en = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY());323 newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY());324 325 500 // find out the movement distance, in metres 326 double distance = Main.getProjection().eastNorth2latlon(initialN1en).greatCircleDistance(Main.getProjection().eastNorth2latlon(n ewN1en));501 double distance = Main.getProjection().eastNorth2latlon(initialN1en).greatCircleDistance(Main.getProjection().eastNorth2latlon(n1movedEn)); 327 502 Main.map.statusLine.setDist(distance); 328 503 updateStatusLine(); 329 504 330 505 Main.map.mapView.setNewCursor(Cursor.MOVE_CURSOR, this); 331 506 332 if (mode == Mode.extrude || mode == Mode.create_new) { 333 //nothing here 334 } else if (mode == Mode.translate_node || mode == Mode.translate) { 335 //move nodes to new position 336 if (moveCommand == null) { 337 //make a new move command 338 moveCommand = new MoveCommand(movingNodeList, bestMovement.getX(), bestMovement.getY()); 339 Main.main.undoRedo.add(moveCommand); 340 } else { 341 //reuse existing move command 342 moveCommand.moveAgainTo(bestMovement.getX(), bestMovement.getY()); 507 if (dualAlignActive) { 508 calculateDualAlignNodesPositions(bestMovement); 509 510 if (mode == Mode.extrude || mode == Mode.create_new) { 511 // nothing here 512 } else if (mode == Mode.translate) { 513 EastNorth movement1 = initialN1en.sub(newN1en); 514 EastNorth movement2 = initialN2en.sub(newN2en); 515 // move nodes to new position 516 if (moveCommand == null || moveCommand2 == null) { 517 // make a new move commands 518 moveCommand = new MoveCommand(movingNodeList.get(0), movement1.getX(), movement1.getY()); 519 moveCommand2 = new MoveCommand(movingNodeList.get(1), movement2.getX(), movement2.getY()); 520 Command c = new SequenceCommand(tr("Extrude Way"), moveCommand, moveCommand2); 521 Main.main.undoRedo.add(c); 522 } else { 523 // reuse existing move commands 524 moveCommand.moveAgainTo(movement1.getX(), movement1.getY()); 525 moveCommand2.moveAgainTo(movement2.getX(), movement2.getY()); 526 } 343 527 } 528 } else { 529 newN1en = n1movedEn; 530 newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY()); 531 532 if (mode == Mode.extrude || mode == Mode.create_new) { 533 //nothing here 534 } else if (mode == Mode.translate_node || mode == Mode.translate) { 535 //move nodes to new position 536 if (moveCommand == null) { 537 //make a new move command 538 moveCommand = new MoveCommand(movingNodeList, bestMovement.getX(), bestMovement.getY()); 539 Main.main.undoRedo.add(moveCommand); 540 } else { 541 //reuse existing move command 542 moveCommand.moveAgainTo(bestMovement.getX(), bestMovement.getY()); 543 } 544 } 344 545 } 345 546 346 547 Main.map.mapView.repaint(); … … 348 549 } 349 550 350 551 /** 351 * Do anything that needs to be done, then switch back to select mode 552 * Does anything that needs to be done, then switches back to select mode. 553 * @param e 352 554 */ 353 @Override public void mouseReleased(MouseEvent e) { 555 @Override 556 public void mouseReleased(MouseEvent e) { 354 557 355 558 if(!Main.map.mapView.isActiveLayerVisible()) 356 559 return; … … 376 579 //the move command is already committed in mouseDragged 377 580 } 378 581 379 boolean alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0; 380 boolean ctrl = (e.getModifiers() & (ActionEvent.CTRL_MASK)) != 0; 381 boolean shift = (e.getModifiers() & (ActionEvent.SHIFT_MASK)) != 0; 582 updateKeyModifiers(e); 382 583 // Switch back into select mode 383 584 Main.map.mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this); 384 585 Main.map.mapView.removeTemporaryLayer(this); … … 391 592 } 392 593 } 393 594 595 // ------------------------------------------------------------------------- 596 // Custom methods 597 // ------------------------------------------------------------------------- 598 394 599 /** 395 * Insert node into nearby segment396 * @param e -current mouse point600 * Inserts node into nearby segment. 601 * @param e current mouse point 397 602 */ 398 603 private void addNewNode(MouseEvent e) { 399 604 // Should maybe do the same as in DrawAction and fetch all nearby segments? … … 411 616 } 412 617 } 413 618 619 /** 620 * Creates a new way that shares segment with selected way. 621 */ 414 622 private void createNewRectangle() { 415 623 if (selectedSegment == null) return; 416 624 // crete a new rectangle … … 434 642 } 435 643 436 644 /** 437 * Do actual extrusion of @field selectedSegment645 * Does actual extrusion of {@link #selectedSegment}. 438 646 */ 439 647 private void performExtrusion() { 440 648 // create extrusion … … 449 657 boolean nodeOverlapsSegment = prevNode != null && Geometry.segmentsParallel(initialN1en, prevNode.getEastNorth(), initialN1en, newN1en); 450 658 // segmentAngleZero marks subset of nodeOverlapsSegment. nodeOverlapsSegment is true if angle between segments is 0 or PI, segmentAngleZero only if angle is 0 451 659 boolean segmentAngleZero = prevNode != null && Math.abs(Geometry.getCornerAngle(prevNode.getEastNorth(), initialN1en, newN1en)) < 1e-5; 452 boolean hasOtherWays = this.hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.way);660 boolean hasOtherWays = hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.way); 453 661 454 662 if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) { 455 663 //move existing node … … 513 721 } 514 722 515 723 /** 516 * This method tests if a nodehas other ways apart from the given one.724 * This method tests if {@code node} has other ways apart from the given one. 517 725 * @param node 518 726 * @param myWay 519 * @return true of node belongs only to myWay, false if there are more ways.727 * @return {@code true} if {@code node} belongs only to {@code myWay}, false if there are more ways. 520 728 */ 521 private boolean hasNodeOtherWays(Node node, Way myWay) {729 private static boolean hasNodeOtherWays(Node node, Way myWay) { 522 730 for (OsmPrimitive p : node.getReferrers()) { 523 731 if (p instanceof Way && p.isUsable() && p != myWay) 524 732 return true; … … 527 735 } 528 736 529 737 /** 530 * Determine best movenemnt from initialMousePos to current position @param mouseEn, 531 * choosing one of the directions @field possibleMoveDirections 738 * Determines best movement from {@link #initialMousePos} to current mouse position, 739 * choosing one of the directions from {@link #possibleMoveDirections}. 740 * @param mouseEn current mouse position 532 741 * @return movement vector 533 742 */ 534 743 private EastNorth calculateBestMovement(EastNorth mouseEn) { … … 556 765 } 557 766 } 558 767 return bestMovement; 768 769 559 770 } 560 771 561 772 /*** 562 * This method calculates offset amount by witch to move the given segment perpendicularly for it to be in line with mouse position. 563 * @param segmentP1 564 * @param segmentP2 565 * @param targetPos 773 * This method calculates offset amount by which to move the given segment 774 * perpendicularly for it to be in line with mouse position. 775 * @param segmentP1 segment's first point 776 * @param segmentP2 segment's second point 777 * @param moveDirection direction of movement 778 * @param targetPos mouse position 566 779 * @return offset amount of P1 and P2. 567 780 */ 568 781 private static EastNorth calculateSegmentOffset(EastNorth segmentP1, EastNorth segmentP2, EastNorth moveDirection, … … 582 795 } 583 796 584 797 /** 585 * Gather possible move directions - perpendicular to the selected segment and parallel to neighbor segments 798 * Gathers possible move directions - perpendicular to the selected segment 799 * and parallel to neighboring segments. 586 800 */ 587 801 private void calculatePossibleDirectionsBySegment() { 588 802 // remember initial positions for segment nodes. … … 618 832 } 619 833 620 834 /** 621 * Gather possible move directions - along all adjacent segments835 * Gathers possible move directions - along all adjacent segments. 622 836 */ 623 837 private void calculatePossibleDirectionsByNode() { 624 838 // remember initial positions for segment nodes. … … 639 853 } 640 854 641 855 /** 642 * Gets a node from selected way before given index. 856 * Checks dual alignment conditions: 857 * 1. selected segment has both neighboring segments, 858 * 2. selected segment is not parallel with neighboring segments. 859 * @return {@code true} if dual alignment conditions are satisfied 860 */ 861 private boolean checkDualAlignConditions() { 862 Node prevNode = getPreviousNode(selectedSegment.lowerIndex); 863 Node nextNode = getNextNode(selectedSegment.lowerIndex + 1); 864 if (prevNode == null || nextNode == null) { 865 return false; 866 } 867 868 EastNorth n1en = selectedSegment.getFirstNode().getEastNorth(); 869 EastNorth n2en = selectedSegment.getSecondNode().getEastNorth(); 870 boolean prevSegmentParallel = Geometry.segmentsParallel(n1en, prevNode.getEastNorth(), n1en, n2en); 871 boolean nextSegmentParallel = Geometry.segmentsParallel(n2en, nextNode.getEastNorth(), n1en, n2en); 872 if (prevSegmentParallel || nextSegmentParallel) { 873 return false; 874 } 875 876 return true; 877 } 878 879 /** 880 * Gathers possible move directions - perpendicular to the selected segment only. 881 * Neighboring segments go to {@link #dualAlignSegment1} and {@link #dualAlignSegment2}. 882 */ 883 private void calculatePossibleDirectionsForDualAlign() { 884 // remember initial positions for segment nodes. 885 initialN1en = selectedSegment.getFirstNode().getEastNorth(); 886 initialN2en = selectedSegment.getSecondNode().getEastNorth(); 887 888 // add direction perpendicular to the selected segment 889 possibleMoveDirections = new ArrayList<ReferenceSegment>(); 890 possibleMoveDirections.add(new ReferenceSegment(new EastNorth( 891 initialN1en.getY() - initialN2en.getY(), 892 initialN2en.getX() - initialN1en.getX() 893 ), initialN1en, initialN2en, true)); 894 895 // set neighboring segments 896 Node prevNode = getPreviousNode(selectedSegment.lowerIndex); 897 EastNorth prevNodeEn = prevNode.getEastNorth(); 898 dualAlignSegment1 = new ReferenceSegment(new EastNorth( 899 initialN1en.getX() - prevNodeEn.getX(), 900 initialN1en.getY() - prevNodeEn.getY() 901 ), initialN1en, prevNodeEn, false); 902 903 Node nextNode = getNextNode(selectedSegment.lowerIndex + 1); 904 EastNorth nextNodeEn = nextNode.getEastNorth(); 905 dualAlignSegment2 = new ReferenceSegment(new EastNorth( 906 initialN2en.getX() - nextNodeEn.getX(), 907 initialN2en.getY() - nextNodeEn.getY() 908 ), initialN2en, nextNodeEn, false); 909 } 910 911 /** 912 * Calculates positions of new nodes, aligning them to neighboring segments. 913 * @param movement movement to be used 914 */ 915 private void calculateDualAlignNodesPositions(EastNorth movement) { 916 // new positions of selected segment's nodes, without applying dual alignment 917 EastNorth n1movedEn = new EastNorth(initialN1en.getX() + movement.getX(), initialN1en.getY() + movement.getY()); 918 EastNorth n2movedEn = new EastNorth(initialN2en.getX() + movement.getX(), initialN2en.getY() + movement.getY()); 919 920 // calculate intersections 921 newN1en = Geometry.getLineLineIntersection(n1movedEn, n2movedEn, dualAlignSegment1.p1, dualAlignSegment1.p2); 922 newN2en = Geometry.getLineLineIntersection(n1movedEn, n2movedEn, dualAlignSegment2.p1, dualAlignSegment2.p2); 923 } 924 925 /** 926 * Gets a node index from selected way before given index. 643 927 * @param index index of current node 644 * @return index of previous node or -1if there are no nodes there.928 * @return index of previous node or <code>-1</code> if there are no nodes there. 645 929 */ 646 930 private int getPreviousNodeIndex(int index) { 647 931 if (index > 0) … … 655 939 /** 656 940 * Gets a node from selected way before given index. 657 941 * @param index index of current node 658 * @return previous node or nullif there are no nodes there.942 * @return previous node or <code>null</code> if there are no nodes there. 659 943 */ 660 944 private Node getPreviousNode(int index) { 661 945 int indexPrev = getPreviousNodeIndex(index); … … 667 951 668 952 669 953 /** 670 * Gets a node from selected way after given index.954 * Gets a node index from selected way after given index. 671 955 * @param index index of current node 672 * @return index of next node or -1if there are no nodes there.956 * @return index of next node or <code>-1</code> if there are no nodes there. 673 957 */ 674 958 private int getNextNodeIndex(int index) { 675 959 int count = selectedSegment.way.getNodesCount(); … … 684 968 /** 685 969 * Gets a node from selected way after given index. 686 970 * @param index index of current node 687 * @return next node or nullif there are no nodes there.971 * @return next node or <code>null</code> if there are no nodes there. 688 972 */ 689 973 private Node getNextNode(int index) { 690 974 int indexNext = getNextNodeIndex(index); … … 694 978 return null; 695 979 } 696 980 981 // ------------------------------------------------------------------------- 982 // paint methods 983 // ------------------------------------------------------------------------- 984 697 985 @Override 698 986 public void paint(Graphics2D g, MapView mv, Bounds box) { 699 987 Graphics2D g2 = g; … … 719 1007 b.lineTo(p1.x, p1.y); 720 1008 g2.draw(b); 721 1009 722 if (activeMoveDirection != null) { 1010 if (dualAlignActive) { 1011 // Draw reference ways 1012 drawReferenceSegment(g2, mv, dualAlignSegment1.p1, dualAlignSegment1.p2); 1013 drawReferenceSegment(g2, mv, dualAlignSegment2.p1, dualAlignSegment2.p2); 1014 } else if (activeMoveDirection != null) { 723 1015 // Draw reference way 724 Point pr1 = mv.getPoint(activeMoveDirection.p1); 725 Point pr2 = mv.getPoint(activeMoveDirection.p2); 726 b = new GeneralPath(); 727 b.moveTo(pr1.x, pr1.y); 728 b.lineTo(pr2.x, pr2.y); 729 g2.setColor(helperColor); 730 g2.setStroke(helperStrokeDash); 731 g2.draw(b); 1016 drawReferenceSegment(g2, mv, activeMoveDirection.p1, activeMoveDirection.p2); 732 1017 733 1018 // Draw right angle marker on first node position, only when moving at right angle 734 1019 if (activeMoveDirection.perpendicular) { … … 738 1023 double headingDiff = headingRefWS - headingMoveDir; 739 1024 if (headingDiff < 0) headingDiff += 2 * Math.PI; 740 1025 boolean mirrorRA = Math.abs(headingDiff - Math.PI) > 1e-5; 1026 Point pr1 = mv.getPoint(activeMoveDirection.p1); 741 1027 drawAngleSymbol(g2, pr1, normalUnitVector, mirrorRA); 742 1028 } 743 1029 } … … 753 1039 g2.draw(oldline); 754 1040 } 755 1041 756 if (activeMoveDirection != null) { 1042 if (dualAlignActive) { 1043 // Draw reference ways 1044 drawReferenceSegment(g2, mv, dualAlignSegment1.p1, dualAlignSegment1.p2); 1045 drawReferenceSegment(g2, mv, dualAlignSegment2.p1, dualAlignSegment2.p2); 1046 } else if (activeMoveDirection != null) { 757 1047 758 1048 g2.setColor(helperColor); 759 1049 g2.setStroke(helperStrokeDash); … … 794 1084 return normalUnitVector; 795 1085 } 796 1086 1087 /** 1088 * Draws right angle symbol at specified position. 1089 * @param g2 the Graphics2D object used to draw on 1090 * @param center center point of angle 1091 * @param normal vector of normal 1092 * @param mirror {@code true} if symbol should be mirrored by the normal 1093 */ 797 1094 private void drawAngleSymbol(Graphics2D g2, Point2D center, Point2D normal, boolean mirror) { 798 1095 // EastNorth units per pixel 799 1096 double factor = 1.0/g2.getTransform().getScaleX(); … … 815 1112 } 816 1113 817 1114 /** 818 * Create a new Line that extends off the edge of the viewport in one direction 1115 * Draws given reference segment. 1116 * @param g2 the Graphics2D object used to draw on 1117 * @param mv 1118 * @param p1en segment's first point 1119 * @param p2en segment's second point 1120 */ 1121 private void drawReferenceSegment(Graphics2D g2, MapView mv, EastNorth p1en, EastNorth p2en) 1122 { 1123 Point p1 = mv.getPoint(p1en); 1124 Point p2 = mv.getPoint(p2en); 1125 GeneralPath b = new GeneralPath(); 1126 b.moveTo(p1.x, p1.y); 1127 b.lineTo(p2.x, p2.y); 1128 g2.setColor(helperColor); 1129 g2.setStroke(helperStrokeDash); 1130 g2.draw(b); 1131 } 1132 1133 /** 1134 * Creates a new Line that extends off the edge of the viewport in one direction 819 1135 * @param start The start point of the line 820 1136 * @param unitvector A unit vector denoting the direction of the line 821 1137 * @param g the Graphics2D object it will be used on 1138 * @return created line 822 1139 */ 823 1140 static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) { 824 1141 Rectangle bounds = g.getDeviceConfiguration().getBounds();