Ticket #7991: 7991-DualAlign.patch
File 7991-DualAlign.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</code>, when extruding create new node(s) even if segments are parallel. 66 77 */ 67 78 private boolean alwaysCreateNodes = false; 68 79 private boolean nodeDragWithoutCtrl; … … 91 102 /** 92 103 * Collection of nodes that is moved 93 104 */ 94 private Collection<OsmPrimitive> movingNodeList;105 private ArrayList<OsmPrimitive> movingNodeList; 95 106 96 107 /** 97 108 * The direction that is currently active. … … 122 133 * the command that performed last move. 123 134 */ 124 135 private MoveCommand moveCommand; 136 /** 137 * The command used for dual alignment movement. 138 * Needs to be separate, due to two nodes moving in different directions. 139 */ 140 private MoveCommand moveCommand2; 125 141 126 142 /** The cursor for the 'create_new' mode. */ 127 143 private final Cursor cursorCreateNew; … … 132 148 /** The cursor for the 'alwaysCreateNodes' submode. */ 133 149 private final Cursor cursorCreateNodes; 134 150 135 private class ReferenceSegment {151 private static class ReferenceSegment { 136 152 public final EastNorth en; 137 153 public final EastNorth p1; 138 154 public final EastNorth p2; … … 144 160 this.p2 = p2; 145 161 this.perpendicular = perpendicular; 146 162 } 163 164 @Override 165 public String toString() { 166 return "ReferenceSegment[en=" + en + ", p1=" + p1 + ", p2=" + p2 + ", perp=" + perpendicular + "]"; 167 } 147 168 } 148 169 149 /** 150 * This listener is used to indicate the 'create_new' mode, if the Alt modifier is pressed. 151 */ 152 private final AWTEventListener altKeyListener = new AWTEventListener() { 170 // Dual alignment mode stuff 171 /** <code>true</code>, if dual alignment mode is enabled. User wants following extrude to be dual aligned. */ 172 private boolean dualAlignEnabled; 173 /** <code>true</code>, if dual alignment is active. User is dragging the mouse, required conditions are met. Treat {@link #mode} (extrude/translate/create_new) as dual aligned. */ 174 private boolean dualAlignActive; 175 /** Dual alignment reference segments */ 176 private ReferenceSegment dualAlignSegment1, dualAlignSegment2; 177 // Dual alignment UI stuff 178 private final DualAlignChangeAction dualAlignChangeAction; 179 private final JCheckBoxMenuItem dualAlignCheckboxMenuItem; 180 private final Shortcut dualAlignShortcut; 181 private boolean useRepeatedShortcut; 182 183 private class DualAlignChangeAction extends JosmAction { 184 public DualAlignChangeAction() { 185 super(tr("Dual alignment"), "mapmode/extrude/dualalign", 186 tr("Switch dual alignment mode while extruding"), null, false); 187 putValue("help", ht("/Action/Extrude/DualAlign")); 188 } 189 153 190 @Override 154 public void eventDispatched(AWTEvent e) { 155 if(Main.map == null || Main.map.mapView == null || !Main.map.mapView.isActiveLayerDrawable()) 156 return; 157 InputEvent ie = (InputEvent) e; 158 boolean alt = (ie.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0; 159 boolean ctrl = (ie.getModifiers() & (ActionEvent.CTRL_MASK)) != 0; 160 boolean shift = (ie.getModifiers() & (ActionEvent.SHIFT_MASK)) != 0; 161 if (mode == Mode.select) { 162 Main.map.mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this); 163 } 191 public void actionPerformed(ActionEvent e) { 192 toggleDualAlign(); 164 193 } 165 } ;194 } 166 195 167 196 /** 168 * Create a new SelectAction197 * Creates a new ExtrudeAction 169 198 * @param mapFrame The MapFrame this action belongs to. 170 199 */ 171 200 public ExtrudeAction(MapFrame mapFrame) { … … 177 206 cursorCreateNew = ImageProvider.getCursor("normal", "rectangle_plus"); 178 207 cursorTranslate = ImageProvider.getCursor("normal", "rectangle_move"); 179 208 cursorCreateNodes = ImageProvider.getCursor("normal", "rectangle_plussmall"); 209 210 dualAlignEnabled = false; 211 dualAlignChangeAction = new DualAlignChangeAction(); 212 dualAlignCheckboxMenuItem = addDualAlignMenuItem(); 213 dualAlignCheckboxMenuItem.getAction().setEnabled(false); 214 dualAlignCheckboxMenuItem.setState(dualAlignEnabled); 215 dualAlignShortcut = Shortcut.registerShortcut("mapmode:extrudedualalign", 216 tr("Mode: {0}", tr("Extrude Dual alignment")), KeyEvent.CHAR_UNDEFINED, Shortcut.NONE); 217 useRepeatedShortcut = Main.pref.getBoolean("extrude.dualalign.toggleOnRepeatedX", true); 218 timer = new Timer(0, new ActionListener() { 219 @Override 220 public void actionPerformed(ActionEvent ae) { 221 timer.stop(); 222 if (set.remove(releaseEvent.getKeyCode())) { 223 doKeyReleaseEvent(releaseEvent); 224 } 225 } 226 }); 180 227 } 181 228 182 @Override public String getModeHelpText() { 183 if (mode == Mode.translate) 184 return tr("Move a segment along its normal, then release the mouse button."); 185 else if (mode == Mode.extrude) 186 return tr("Draw a rectangle of the desired size, then release the mouse button."); 187 else if (mode == Mode.create_new) 188 return tr("Draw a rectangle of the desired size, then release the mouse button."); 189 else 190 return tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal, " + 191 "Alt-drag to create a new rectangle, double click to add a new node."); 229 @Override 230 public void destroy() { 231 super.destroy(); 232 dualAlignChangeAction.destroy(); 192 233 } 193 234 194 @Override public boolean layerIsSupported(Layer l) { 235 private JCheckBoxMenuItem addDualAlignMenuItem() { 236 int n = Main.main.menu.editMenu.getItemCount(); 237 for (int i = n-1; i>0; i--) { 238 JMenuItem item = Main.main.menu.editMenu.getItem(i); 239 if (item != null && item.getAction() != null && item.getAction() instanceof DualAlignChangeAction) { 240 Main.main.menu.editMenu.remove(i); 241 } 242 } 243 return MainMenu.addWithCheckbox(Main.main.menu.editMenu, dualAlignChangeAction, MainMenu.WINDOW_MENU_GROUP.VOLATILE); 244 } 245 246 // ------------------------------------------------------------------------- 247 // Mode methods 248 // ------------------------------------------------------------------------- 249 250 @Override 251 public String getModeHelpText() { 252 String rv; 253 if (mode == Mode.select) { 254 rv = tr("Drag a way segment to make a rectangle. Ctrl-drag to move a segment along its normal, " + 255 "Alt-drag to create a new rectangle, double click to add a new node."); 256 if (dualAlignEnabled) 257 rv += " " + tr("Dual alignment active."); 258 } else { 259 if (mode == Mode.translate) 260 rv = tr("Move a segment along its normal, then release the mouse button."); 261 else if (mode == Mode.translate_node) 262 rv = tr("Move the node along one of the segments, then release the mouse button."); 263 else if (mode == Mode.extrude) 264 rv = tr("Draw a rectangle of the desired size, then release the mouse button."); 265 else if (mode == Mode.create_new) 266 rv = tr("Draw a rectangle of the desired size, then release the mouse button."); 267 else { 268 Main.warn("Extrude: unknown mode " + mode); 269 rv = ""; 270 } 271 if (dualAlignActive) 272 rv += " " + tr("Dual alignment active."); 273 } 274 return rv; 275 } 276 277 @Override 278 public boolean layerIsSupported(Layer l) { 195 279 return l instanceof OsmDataLayer; 196 280 } 197 281 198 @Override public void enterMode() { 282 @Override 283 public void enterMode() { 199 284 super.enterMode(); 200 285 Main.map.mapView.addMouseListener(this); 201 286 Main.map.mapView.addMouseMotionListener(this); … … 213 298 nodeDragWithoutCtrl = Main.pref.getBoolean("extrude.drag-nodes-without-ctrl", false); 214 299 oldLineStroke = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.ctrl.stroke.old-line", "1")); 215 300 mainStroke = GuiHelper.getCustomizedStroke(Main.pref.get("extrude.stroke.main", "3")); 301 dualAlignCheckboxMenuItem.getAction().setEnabled(true); 216 302 } 217 303 218 @Override public void exitMode() { 304 @Override 305 public void exitMode() { 219 306 Main.map.mapView.removeMouseListener(this); 220 307 Main.map.mapView.removeMouseMotionListener(this); 221 308 Main.map.mapView.removeTemporaryLayer(this); 309 dualAlignCheckboxMenuItem.getAction().setEnabled(false); 222 310 try { 223 311 Toolkit.getDefaultToolkit().removeAWTEventListener(altKeyListener); 224 312 } catch (SecurityException ex) { … … 226 314 super.exitMode(); 227 315 } 228 316 317 // ------------------------------------------------------------------------- 318 // Event handlers 319 // ------------------------------------------------------------------------- 320 229 321 /** 230 * If the left mouse button is pressed over a segment, switch231 * to either extrude, translate or create_new mode depending on whether Ctrl or Alt is held.322 * This listener is used to indicate different modes via cursor when the Alt/Ctrl/Shift modifier is pressed, 323 * and for listening to dual alignment shortcuts. 232 324 */ 233 @Override public void mousePressed(MouseEvent e) { 325 private final AWTEventListener altKeyListener = new AWTEventListener() { 326 @Override 327 public void eventDispatched(AWTEvent e) { 328 if(Main.map == null || Main.map.mapView == null || !Main.map.mapView.isActiveLayerDrawable()) 329 return; 330 InputEvent ie = (InputEvent) e; 331 boolean alt = (ie.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0; 332 boolean ctrl = (ie.getModifiers() & (ActionEvent.CTRL_MASK)) != 0; 333 boolean shift = (ie.getModifiers() & (ActionEvent.SHIFT_MASK)) != 0; 334 if (mode == Mode.select) { 335 Main.map.mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this); 336 } 337 if (e instanceof KeyEvent) { 338 KeyEvent ke = (KeyEvent) e; 339 if (dualAlignShortcut.isEvent(ke) || (useRepeatedShortcut && getShortcut().isEvent(ke))) { 340 Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 341 if (SwingUtilities.getWindowAncestor(focused) instanceof JFrame) { 342 processKeyEvent(ke); 343 } 344 } 345 } 346 } 347 }; 348 349 // events for crossplatform key holding processing 350 // thanks to http://www.arco.in-berlin.de/keyevent.html 351 private final TreeSet<Integer> set = new TreeSet<Integer>(); 352 private KeyEvent releaseEvent; 353 private Timer timer; 354 private void processKeyEvent(KeyEvent e) { 355 if (!dualAlignShortcut.isEvent(e) && !(useRepeatedShortcut && getShortcut().isEvent(e))) 356 return; 357 358 if (e.getID() == KeyEvent.KEY_PRESSED) { 359 if (timer.isRunning()) { 360 timer.stop(); 361 } else if (set.add((e.getKeyCode()))) { 362 doKeyPressEvent(e); 363 } 364 } else if (e.getID() == KeyEvent.KEY_RELEASED) { 365 if (timer.isRunning()) { 366 timer.stop(); 367 if (set.remove(e.getKeyCode())) { 368 doKeyReleaseEvent(e); 369 } 370 } else { 371 releaseEvent = e; 372 timer.restart(); 373 } 374 } 375 } 376 377 private void doKeyPressEvent(KeyEvent e) { 378 } 379 380 private void doKeyReleaseEvent(KeyEvent e) { 381 toggleDualAlign(); 382 } 383 384 /** 385 * Toggles dual alignment mode. 386 */ 387 private void toggleDualAlign() { 388 dualAlignEnabled = !dualAlignEnabled; 389 dualAlignCheckboxMenuItem.setState(dualAlignEnabled); 390 updateStatusLine(); 391 } 392 393 /** 394 * If the left mouse button is pressed over a segment or a node, switches 395 * to appropriate {@link #mode}, depending on Ctrl/Alt/Shift modifiers and 396 * {@link #dualAlignEnabled}. 397 * @param e 398 */ 399 @Override 400 public void mousePressed(MouseEvent e) { 234 401 if(!Main.map.mapView.isActiveLayerVisible()) 235 402 return; 236 403 if (!(Boolean)this.getValue("active")) … … 257 424 return; 258 425 } 259 426 mode = Mode.translate_node; 427 dualAlignActive = false; 260 428 } 261 429 } else { 262 430 // Otherwise switch to another mode 431 if (dualAlignEnabled && checkDualAlignConditions()) { 432 dualAlignActive = true; 433 calculatePossibleDirectionsForDualAlign(); 434 } else { 435 dualAlignActive = false; 436 calculatePossibleDirectionsBySegment(); 437 } 263 438 if (ctrl) { 264 439 mode = Mode.translate; 265 440 movingNodeList = new ArrayList<OsmPrimitive>(); … … 275 450 getCurrentDataSet().setSelected(selectedSegment.way); 276 451 alwaysCreateNodes = shift; 277 452 } 278 calculatePossibleDirectionsBySegment();279 453 } 280 454 281 455 // Signifies that nothing has happened yet … … 282 456 newN1en = null; 283 457 newN2en = null; 284 458 moveCommand = null; 459 moveCommand2 = null; 285 460 286 461 Main.map.mapView.addTemporaryLayer(this); 287 462 … … 296 471 } 297 472 298 473 /** 299 * Perform action depending on what mode we're in. 474 * Performs action depending on what {@link #mode} we're in. 475 * @param e 300 476 */ 301 @Override public void mouseDragged(MouseEvent e) { 477 @Override 478 public void mouseDragged(MouseEvent e) { 302 479 if(!Main.map.mapView.isActiveLayerVisible()) 303 480 return; 304 481 … … 313 490 314 491 EastNorth mouseEn = Main.map.mapView.getEastNorth(e.getPoint().x, e.getPoint().y); 315 492 EastNorth bestMovement = calculateBestMovement(mouseEn); 493 EastNorth n1movedEn = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY()); 316 494 317 newN1en = new EastNorth(initialN1en.getX() + bestMovement.getX(), initialN1en.getY() + bestMovement.getY());318 newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY());319 320 495 // find out the movement distance, in metres 321 double distance = Main.getProjection().eastNorth2latlon(initialN1en).greatCircleDistance(Main.getProjection().eastNorth2latlon(n ewN1en));496 double distance = Main.getProjection().eastNorth2latlon(initialN1en).greatCircleDistance(Main.getProjection().eastNorth2latlon(n1movedEn)); 322 497 Main.map.statusLine.setDist(distance); 323 498 updateStatusLine(); 324 499 325 500 Main.map.mapView.setNewCursor(Cursor.MOVE_CURSOR, this); 326 501 327 if (mode == Mode.extrude || mode == Mode.create_new) { 328 //nothing here 329 } else if (mode == Mode.translate_node || mode == Mode.translate) { 330 //move nodes to new position 331 if (moveCommand == null) { 332 //make a new move command 333 moveCommand = new MoveCommand(movingNodeList, bestMovement.getX(), bestMovement.getY()); 334 Main.main.undoRedo.add(moveCommand); 335 } else { 336 //reuse existing move command 337 moveCommand.moveAgainTo(bestMovement.getX(), bestMovement.getY()); 502 if (dualAlignActive) { 503 calculateDualAlignNodesPositions(bestMovement); 504 505 if (mode == Mode.extrude || mode == Mode.create_new) { 506 // nothing here 507 } else if (mode == Mode.translate) { 508 EastNorth movement1 = initialN1en.sub(newN1en); 509 EastNorth movement2 = initialN2en.sub(newN2en); 510 // move nodes to new position 511 if (moveCommand == null || moveCommand2 == null) { 512 // make a new move commands 513 moveCommand = new MoveCommand(movingNodeList.get(0), movement1.getX(), movement1.getY()); 514 moveCommand2 = new MoveCommand(movingNodeList.get(1), movement2.getX(), movement2.getY()); 515 Command c = new SequenceCommand(tr("Extrude Way"), moveCommand, moveCommand2); 516 Main.main.undoRedo.add(c); 517 } else { 518 // reuse existing move commands 519 moveCommand.moveAgainTo(movement1.getX(), movement1.getY()); 520 moveCommand2.moveAgainTo(movement2.getX(), movement2.getY()); 521 } 338 522 } 523 } else { 524 newN1en = n1movedEn; 525 newN2en = new EastNorth(initialN2en.getX() + bestMovement.getX(), initialN2en.getY() + bestMovement.getY()); 526 527 if (mode == Mode.extrude || mode == Mode.create_new) { 528 //nothing here 529 } else if (mode == Mode.translate_node || mode == Mode.translate) { 530 //move nodes to new position 531 if (moveCommand == null) { 532 //make a new move command 533 moveCommand = new MoveCommand(movingNodeList, bestMovement.getX(), bestMovement.getY()); 534 Main.main.undoRedo.add(moveCommand); 535 } else { 536 //reuse existing move command 537 moveCommand.moveAgainTo(bestMovement.getX(), bestMovement.getY()); 538 } 539 } 339 540 } 340 541 341 542 Main.map.mapView.repaint(); … … 343 544 } 344 545 345 546 /** 346 * Do anything that needs to be done, then switch back to select mode 547 * Does anything that needs to be done, then switches back to select mode. 548 * @param e 347 549 */ 348 @Override public void mouseReleased(MouseEvent e) { 550 @Override 551 public void mouseReleased(MouseEvent e) { 349 552 350 553 if(!Main.map.mapView.isActiveLayerVisible()) 351 554 return; … … 371 574 //the move command is already committed in mouseDragged 372 575 } 373 576 374 boolean alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0; 375 boolean ctrl = (e.getModifiers() & (ActionEvent.CTRL_MASK)) != 0; 376 boolean shift = (e.getModifiers() & (ActionEvent.SHIFT_MASK)) != 0; 577 updateKeyModifiers(e); 377 578 // Switch back into select mode 378 579 Main.map.mapView.setNewCursor(ctrl ? cursorTranslate : alt ? cursorCreateNew : shift ? cursorCreateNodes : cursor, this); 379 580 Main.map.mapView.removeTemporaryLayer(this); … … 386 587 } 387 588 } 388 589 590 // ------------------------------------------------------------------------- 591 // Custom methods 592 // ------------------------------------------------------------------------- 593 389 594 /** 390 * Insert node into nearby segment391 * @param e -current mouse point595 * Inserts node into nearby segment. 596 * @param e current mouse point 392 597 */ 393 598 private void addNewNode(MouseEvent e) { 394 599 // Should maybe do the same as in DrawAction and fetch all nearby segments? … … 406 611 } 407 612 } 408 613 614 /** 615 * Creates a new way that shares segment with selected way. 616 */ 409 617 private void createNewRectangle() { 410 618 if (selectedSegment == null) return; 411 619 // crete a new rectangle … … 429 637 } 430 638 431 639 /** 432 * Do actual extrusion of @field selectedSegment640 * Does actual extrusion of {@link #selectedSegment}. 433 641 */ 434 642 private void performExtrusion() { 435 643 // create extrusion … … 442 650 //find if the new points overlap existing segments (in case of 90 degree angles) 443 651 Node prevNode = getPreviousNode(selectedSegment.lowerIndex); 444 652 boolean nodeOverlapsSegment = prevNode != null && Geometry.segmentsParallel(initialN1en, prevNode.getEastNorth(), initialN1en, newN1en); 445 boolean hasOtherWays = this.hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.way);653 boolean hasOtherWays = hasNodeOtherWays(selectedSegment.getFirstNode(), selectedSegment.way); 446 654 447 655 if (nodeOverlapsSegment && !alwaysCreateNodes && !hasOtherWays) { 448 656 //move existing node … … 489 697 } 490 698 491 699 /** 492 * This method tests if a nodehas other ways apart from the given one.700 * This method tests if <code>node</code> has other ways apart from the given one. 493 701 * @param node 494 702 * @param myWay 495 * @return true of node belongs only to myWay, false if there are more ways.703 * @return <code>true</code> if <code>node</code> belongs only to <code>myWay</code>, false if there are more ways. 496 704 */ 497 private boolean hasNodeOtherWays(Node node, Way myWay) {705 private static boolean hasNodeOtherWays(Node node, Way myWay) { 498 706 for (OsmPrimitive p : node.getReferrers()) { 499 707 if (p instanceof Way && p.isUsable() && p != myWay) 500 708 return true; … … 503 711 } 504 712 505 713 /** 506 * Determine best movenemnt from initialMousePos to current position @param mouseEn, 507 * choosing one of the directions @field possibleMoveDirections 714 * Determines best movement from {@link #initialMousePos} to current mouse position, 715 * choosing one of the directions from {@link #possibleMoveDirections}. 716 * @param mouseEn current mouse position 508 717 * @return movement vector 509 718 */ 510 719 private EastNorth calculateBestMovement(EastNorth mouseEn) { … … 532 741 } 533 742 } 534 743 return bestMovement; 744 745 535 746 } 536 747 537 748 /*** 538 * This method calculates offset amount by witch to move the given segment perpendicularly for it to be in line with mouse position. 539 * @param segmentP1 540 * @param segmentP2 541 * @param targetPos 749 * This method calculates offset amount by which to move the given segment 750 * perpendicularly for it to be in line with mouse position. 751 * @param segmentP1 segment's first point 752 * @param segmentP2 segment's second point 753 * @param moveDirection direction of movement 754 * @param targetPos mouse position 542 755 * @return offset amount of P1 and P2. 543 756 */ 544 757 private static EastNorth calculateSegmentOffset(EastNorth segmentP1, EastNorth segmentP2, EastNorth moveDirection, … … 558 771 } 559 772 560 773 /** 561 * Gather possible move directions - perpendicular to the selected segment and parallel to neighbor segments 774 * Gathers possible move directions - perpendicular to the selected segment 775 * and parallel to neighboring segments. 562 776 */ 563 777 private void calculatePossibleDirectionsBySegment() { 564 778 // remember initial positions for segment nodes. … … 594 808 } 595 809 596 810 /** 597 * Gather possible move directions - along all adjacent segments811 * Gathers possible move directions - along all adjacent segments. 598 812 */ 599 813 private void calculatePossibleDirectionsByNode() { 600 814 // remember initial positions for segment nodes. … … 615 829 } 616 830 617 831 /** 618 * Gets a node from selected way before given index. 832 * Checks dual alignment conditions: 833 * 1. selected segment has both neighboring segments, 834 * 2. selected segment is not parallel with neighboring segments. 835 * @return <code>true</code> if dual alignment conditions are satisfied 836 */ 837 private boolean checkDualAlignConditions() { 838 Node prevNode = getPreviousNode(selectedSegment.lowerIndex); 839 Node nextNode = getNextNode(selectedSegment.lowerIndex + 1); 840 if (prevNode == null || nextNode == null) { 841 return false; 842 } 843 844 EastNorth n1en = selectedSegment.getFirstNode().getEastNorth(); 845 EastNorth n2en = selectedSegment.getSecondNode().getEastNorth(); 846 boolean prevSegmentParallel = Geometry.segmentsParallel(n1en, prevNode.getEastNorth(), n1en, n2en); 847 boolean nextSegmentParallel = Geometry.segmentsParallel(n2en, nextNode.getEastNorth(), n1en, n2en); 848 if (prevSegmentParallel || nextSegmentParallel) { 849 return false; 850 } 851 852 return true; 853 } 854 855 /** 856 * Gathers possible move directions - perpendicular to the selected segment only. 857 * Neighboring segments go to {@link #dualAlignSegment1} and {@link #dualAlignSegment2}. 858 */ 859 private void calculatePossibleDirectionsForDualAlign() { 860 // remember initial positions for segment nodes. 861 initialN1en = selectedSegment.getFirstNode().getEastNorth(); 862 initialN2en = selectedSegment.getSecondNode().getEastNorth(); 863 864 // add direction perpendicular to the selected segment 865 possibleMoveDirections = new ArrayList<ReferenceSegment>(); 866 possibleMoveDirections.add(new ReferenceSegment(new EastNorth( 867 initialN1en.getY() - initialN2en.getY(), 868 initialN2en.getX() - initialN1en.getX() 869 ), initialN1en, initialN2en, true)); 870 871 // set neighboring segments 872 Node prevNode = getPreviousNode(selectedSegment.lowerIndex); 873 EastNorth prevNodeEn = prevNode.getEastNorth(); 874 dualAlignSegment1 = new ReferenceSegment(new EastNorth( 875 initialN1en.getX() - prevNodeEn.getX(), 876 initialN1en.getY() - prevNodeEn.getY() 877 ), initialN1en, prevNodeEn, false); 878 879 Node nextNode = getNextNode(selectedSegment.lowerIndex + 1); 880 EastNorth nextNodeEn = nextNode.getEastNorth(); 881 dualAlignSegment2 = new ReferenceSegment(new EastNorth( 882 initialN2en.getX() - nextNodeEn.getX(), 883 initialN2en.getY() - nextNodeEn.getY() 884 ), initialN2en, nextNodeEn, false); 885 } 886 887 /** 888 * Calculates positions of new nodes, aligning them to neighboring segments. 889 * @param movement movement to be used 890 */ 891 private void calculateDualAlignNodesPositions(EastNorth movement) { 892 // new positions of selected segment's nodes, without applying dual alignment 893 EastNorth n1movedEn = new EastNorth(initialN1en.getX() + movement.getX(), initialN1en.getY() + movement.getY()); 894 EastNorth n2movedEn = new EastNorth(initialN2en.getX() + movement.getX(), initialN2en.getY() + movement.getY()); 895 896 // calculate intersections 897 newN1en = Geometry.getLineLineIntersection(n1movedEn, n2movedEn, dualAlignSegment1.p1, dualAlignSegment1.p2); 898 newN2en = Geometry.getLineLineIntersection(n1movedEn, n2movedEn, dualAlignSegment2.p1, dualAlignSegment2.p2); 899 } 900 901 /** 902 * Gets a node index from selected way before given index. 619 903 * @param index index of current node 620 * @return index of previous node or -1if there are no nodes there.904 * @return index of previous node or <code>-1</code> if there are no nodes there. 621 905 */ 622 906 private int getPreviousNodeIndex(int index) { 623 907 if (index > 0) … … 631 915 /** 632 916 * Gets a node from selected way before given index. 633 917 * @param index index of current node 634 * @return previous node or nullif there are no nodes there.918 * @return previous node or <code>null</code> if there are no nodes there. 635 919 */ 636 920 private Node getPreviousNode(int index) { 637 921 int indexPrev = getPreviousNodeIndex(index); … … 643 927 644 928 645 929 /** 646 * Gets a node from selected way after given index.930 * Gets a node index from selected way after given index. 647 931 * @param index index of current node 648 * @return index of next node or -1if there are no nodes there.932 * @return index of next node or <code>-1</code> if there are no nodes there. 649 933 */ 650 934 private int getNextNodeIndex(int index) { 651 935 int count = selectedSegment.way.getNodesCount(); … … 660 944 /** 661 945 * Gets a node from selected way after given index. 662 946 * @param index index of current node 663 * @return next node or nullif there are no nodes there.947 * @return next node or <code>null</code> if there are no nodes there. 664 948 */ 665 949 private Node getNextNode(int index) { 666 950 int indexNext = getNextNodeIndex(index); … … 670 954 return null; 671 955 } 672 956 957 // ------------------------------------------------------------------------- 958 // paint methods 959 // ------------------------------------------------------------------------- 960 673 961 @Override 674 962 public void paint(Graphics2D g, MapView mv, Bounds box) { 675 963 Graphics2D g2 = g; … … 695 983 b.lineTo(p1.x, p1.y); 696 984 g2.draw(b); 697 985 698 if (activeMoveDirection != null) { 986 if (dualAlignActive) { 987 // Draw reference ways 988 drawReferenceSegment(g2, mv, dualAlignSegment1.p1, dualAlignSegment1.p2); 989 drawReferenceSegment(g2, mv, dualAlignSegment2.p1, dualAlignSegment2.p2); 990 } else if (activeMoveDirection != null) { 699 991 // Draw reference way 700 Point pr1 = mv.getPoint(activeMoveDirection.p1); 701 Point pr2 = mv.getPoint(activeMoveDirection.p2); 702 b = new GeneralPath(); 703 b.moveTo(pr1.x, pr1.y); 704 b.lineTo(pr2.x, pr2.y); 705 g2.setColor(helperColor); 706 g2.setStroke(helperStrokeDash); 707 g2.draw(b); 992 drawReferenceSegment(g2, mv, activeMoveDirection.p1, activeMoveDirection.p2); 708 993 709 994 // Draw right angle marker on first node position, only when moving at right angle 710 995 if (activeMoveDirection.perpendicular) { … … 714 999 double headingDiff = headingRefWS - headingMoveDir; 715 1000 if (headingDiff < 0) headingDiff += 2 * Math.PI; 716 1001 boolean mirrorRA = Math.abs(headingDiff - Math.PI) > 1e-5; 1002 Point pr1 = mv.getPoint(activeMoveDirection.p1); 717 1003 drawAngleSymbol(g2, pr1, normalUnitVector, mirrorRA); 718 1004 } 719 1005 } … … 729 1015 g2.draw(oldline); 730 1016 } 731 1017 732 if (activeMoveDirection != null) { 1018 if (dualAlignActive) { 1019 // Draw reference ways 1020 drawReferenceSegment(g2, mv, dualAlignSegment1.p1, dualAlignSegment1.p2); 1021 drawReferenceSegment(g2, mv, dualAlignSegment2.p1, dualAlignSegment2.p2); 1022 } else if (activeMoveDirection != null) { 733 1023 734 1024 g2.setColor(helperColor); 735 1025 g2.setStroke(helperStrokeDash); … … 770 1060 return normalUnitVector; 771 1061 } 772 1062 1063 /** 1064 * Draws right angle symbol at specified position. 1065 * @param g2 the Graphics2D object used to draw on 1066 * @param center center point of angle 1067 * @param normal vector of normal 1068 * @param mirror <code>true</code> if symbol should be mirrored by the normal 1069 */ 773 1070 private void drawAngleSymbol(Graphics2D g2, Point2D center, EastNorth normal, boolean mirror) { 774 1071 // EastNorth units per pixel 775 1072 double factor = 1.0/g2.getTransform().getScaleX(); … … 791 1088 } 792 1089 793 1090 /** 794 * Create a new Line that extends off the edge of the viewport in one direction 1091 * Draws given reference segment. 1092 * @param g2 the Graphics2D object used to draw on 1093 * @param mv 1094 * @param p1en segment's first point 1095 * @param p2en segment's second point 1096 */ 1097 private void drawReferenceSegment(Graphics2D g2, MapView mv, EastNorth p1en, EastNorth p2en) 1098 { 1099 Point p1 = mv.getPoint(p1en); 1100 Point p2 = mv.getPoint(p2en); 1101 GeneralPath b = new GeneralPath(); 1102 b.moveTo(p1.x, p1.y); 1103 b.lineTo(p2.x, p2.y); 1104 g2.setColor(helperColor); 1105 g2.setStroke(helperStrokeDash); 1106 g2.draw(b); 1107 } 1108 1109 /** 1110 * Creates a new Line that extends off the edge of the viewport in one direction 795 1111 * @param start The start point of the line 796 1112 * @param unitvector A unit vector denoting the direction of the line 797 1113 * @param g the Graphics2D object it will be used on 1114 * @return created line 798 1115 */ 799 1116 static private Line2D createSemiInfiniteLine(Point2D start, Point2D unitvector, Graphics2D g) { 800 1117 Rectangle bounds = g.getDeviceConfiguration().getBounds();