001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions.mapmode; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006import static org.openstreetmap.josm.tools.I18n.trn; 007 008import java.awt.BasicStroke; 009import java.awt.Color; 010import java.awt.Cursor; 011import java.awt.Graphics2D; 012import java.awt.Point; 013import java.awt.event.KeyEvent; 014import java.awt.event.MouseEvent; 015import java.util.ArrayList; 016import java.util.Collection; 017import java.util.Collections; 018import java.util.LinkedList; 019import java.util.List; 020 021import javax.swing.JOptionPane; 022 023import org.openstreetmap.josm.Main; 024import org.openstreetmap.josm.command.AddCommand; 025import org.openstreetmap.josm.command.ChangeCommand; 026import org.openstreetmap.josm.command.Command; 027import org.openstreetmap.josm.command.DeleteCommand; 028import org.openstreetmap.josm.command.MoveCommand; 029import org.openstreetmap.josm.command.SequenceCommand; 030import org.openstreetmap.josm.data.Bounds; 031import org.openstreetmap.josm.data.coor.EastNorth; 032import org.openstreetmap.josm.data.osm.DataSelectionListener; 033import org.openstreetmap.josm.data.osm.DataSet; 034import org.openstreetmap.josm.data.osm.Node; 035import org.openstreetmap.josm.data.osm.OsmPrimitive; 036import org.openstreetmap.josm.data.osm.Way; 037import org.openstreetmap.josm.data.osm.WaySegment; 038import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 039import org.openstreetmap.josm.data.preferences.CachingProperty; 040import org.openstreetmap.josm.data.preferences.IntegerProperty; 041import org.openstreetmap.josm.data.preferences.NamedColorProperty; 042import org.openstreetmap.josm.data.preferences.StrokeProperty; 043import org.openstreetmap.josm.gui.MainApplication; 044import org.openstreetmap.josm.gui.MapFrame; 045import org.openstreetmap.josm.gui.MapView; 046import org.openstreetmap.josm.gui.draw.MapViewPath; 047import org.openstreetmap.josm.gui.draw.SymbolShape; 048import org.openstreetmap.josm.gui.layer.AbstractMapViewPaintable; 049import org.openstreetmap.josm.gui.layer.Layer; 050import org.openstreetmap.josm.gui.util.ModifierExListener; 051import org.openstreetmap.josm.tools.ImageProvider; 052import org.openstreetmap.josm.tools.Logging; 053import org.openstreetmap.josm.tools.Pair; 054import org.openstreetmap.josm.tools.Shortcut; 055 056/** 057 * A special map mode that is optimized for improving way geometry. 058 * (by efficiently moving, adding and deleting way-nodes) 059 * 060 * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011 061 */ 062public class ImproveWayAccuracyAction extends MapMode implements DataSelectionListener, ModifierExListener { 063 064 private static final String CROSSHAIR = /* ICON(cursor/)*/ "crosshair"; 065 066 enum State { 067 SELECTING, IMPROVING 068 } 069 070 private State state; 071 072 private MapView mv; 073 074 private static final long serialVersionUID = 42L; 075 076 private transient Way targetWay; 077 private transient Node candidateNode; 078 private transient WaySegment candidateSegment; 079 080 private Point mousePos; 081 private boolean dragging; 082 083 private final Cursor cursorSelect = ImageProvider.getCursor(/* ICON(cursor/)*/ "normal", /* ICON(cursor/modifier/)*/ "mode"); 084 private final Cursor cursorSelectHover = ImageProvider.getCursor(/* ICON(cursor/)*/ "hand", /* ICON(cursor/modifier/)*/ "mode"); 085 private final Cursor cursorImprove = ImageProvider.getCursor(CROSSHAIR, null); 086 private final Cursor cursorImproveAdd = ImageProvider.getCursor(CROSSHAIR, /* ICON(cursor/modifier/)*/ "addnode"); 087 private final Cursor cursorImproveDelete = ImageProvider.getCursor(CROSSHAIR, /* ICON(cursor/modifier/)*/ "delete_node"); 088 private final Cursor cursorImproveAddLock = ImageProvider.getCursor(CROSSHAIR, /* ICON(cursor/modifier/)*/ "add_node_lock"); 089 private final Cursor cursorImproveLock = ImageProvider.getCursor(CROSSHAIR, /* ICON(cursor/modifier/)*/ "lock"); 090 091 private Color guideColor; 092 093 private static final CachingProperty<BasicStroke> SELECT_TARGET_WAY_STROKE 094 = new StrokeProperty("improvewayaccuracy.stroke.select-target", "2").cached(); 095 private static final CachingProperty<BasicStroke> MOVE_NODE_STROKE 096 = new StrokeProperty("improvewayaccuracy.stroke.move-node", "1 6").cached(); 097 private static final CachingProperty<BasicStroke> MOVE_NODE_INTERSECTING_STROKE 098 = new StrokeProperty("improvewayaccuracy.stroke.move-node-intersecting", "1 2 6").cached(); 099 private static final CachingProperty<BasicStroke> ADD_NODE_STROKE 100 = new StrokeProperty("improvewayaccuracy.stroke.add-node", "1").cached(); 101 private static final CachingProperty<BasicStroke> DELETE_NODE_STROKE 102 = new StrokeProperty("improvewayaccuracy.stroke.delete-node", "1").cached(); 103 private static final CachingProperty<Integer> DOT_SIZE 104 = new IntegerProperty("improvewayaccuracy.dot-size", 6).cached(); 105 106 private boolean selectionChangedBlocked; 107 108 protected String oldModeHelpText; 109 110 private final transient AbstractMapViewPaintable temporaryLayer = new AbstractMapViewPaintable() { 111 @Override 112 public void paint(Graphics2D g, MapView mv, Bounds bbox) { 113 ImproveWayAccuracyAction.this.paint(g, mv, bbox); 114 } 115 }; 116 117 /** 118 * Constructs a new {@code ImproveWayAccuracyAction}. 119 * @since 11713 120 */ 121 public ImproveWayAccuracyAction() { 122 super(tr("Improve Way Accuracy"), "improvewayaccuracy", 123 tr("Improve Way Accuracy mode"), 124 Shortcut.registerShortcut("mapmode:ImproveWayAccuracy", 125 tr("Mode: {0}", tr("Improve Way Accuracy")), 126 KeyEvent.VK_W, Shortcut.DIRECT), Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); 127 128 readPreferences(); 129 } 130 131 // ------------------------------------------------------------------------- 132 // Mode methods 133 // ------------------------------------------------------------------------- 134 @Override 135 public void enterMode() { 136 if (!isEnabled()) { 137 return; 138 } 139 super.enterMode(); 140 readPreferences(); 141 142 MapFrame map = MainApplication.getMap(); 143 mv = map.mapView; 144 mousePos = null; 145 oldModeHelpText = ""; 146 147 if (getLayerManager().getEditDataSet() == null) { 148 return; 149 } 150 151 updateStateByCurrentSelection(); 152 153 map.mapView.addMouseListener(this); 154 map.mapView.addMouseMotionListener(this); 155 map.mapView.addTemporaryLayer(temporaryLayer); 156 SelectionEventManager.getInstance().addSelectionListener(this); 157 158 map.keyDetector.addModifierExListener(this); 159 } 160 161 @Override 162 protected void readPreferences() { 163 guideColor = new NamedColorProperty(marktr("improve way accuracy helper line"), Color.RED).get(); 164 } 165 166 @Override 167 public void exitMode() { 168 super.exitMode(); 169 170 MapFrame map = MainApplication.getMap(); 171 map.mapView.removeMouseListener(this); 172 map.mapView.removeMouseMotionListener(this); 173 map.mapView.removeTemporaryLayer(temporaryLayer); 174 SelectionEventManager.getInstance().removeSelectionListener(this); 175 176 map.keyDetector.removeModifierExListener(this); 177 temporaryLayer.invalidate(); 178 } 179 180 @Override 181 protected void updateStatusLine() { 182 String newModeHelpText = getModeHelpText(); 183 if (!newModeHelpText.equals(oldModeHelpText)) { 184 oldModeHelpText = newModeHelpText; 185 MapFrame map = MainApplication.getMap(); 186 map.statusLine.setHelpText(newModeHelpText); 187 map.statusLine.repaint(); 188 } 189 } 190 191 @Override 192 public String getModeHelpText() { 193 if (state == State.SELECTING) { 194 if (targetWay != null) { 195 return tr("Click on the way to start improving its shape."); 196 } else { 197 return tr("Select a way that you want to make more accurate."); 198 } 199 } else { 200 if (ctrl) { 201 return tr("Click to add a new node. Release Ctrl to move existing nodes or hold Alt to delete."); 202 } else if (alt) { 203 return tr("Click to delete the highlighted node. Release Alt to move existing nodes or hold Ctrl to add new nodes."); 204 } else { 205 return tr("Click to move the highlighted node. Hold Ctrl to add new nodes, or Alt to delete."); 206 } 207 } 208 } 209 210 @Override 211 public boolean layerIsSupported(Layer l) { 212 return isEditableDataLayer(l); 213 } 214 215 @Override 216 protected void updateEnabledState() { 217 setEnabled(getLayerManager().getEditLayer() != null); 218 } 219 220 // ------------------------------------------------------------------------- 221 // MapViewPaintable methods 222 // ------------------------------------------------------------------------- 223 /** 224 * Redraws temporary layer. Highlights targetWay in select mode. Draws 225 * preview lines in improve mode and highlights the candidateNode 226 * @param g The graphics 227 * @param mv The map view 228 * @param bbox The bounding box 229 */ 230 public void paint(Graphics2D g, MapView mv, Bounds bbox) { 231 if (mousePos == null) { 232 return; 233 } 234 235 g.setColor(guideColor); 236 237 if (state == State.SELECTING && targetWay != null) { 238 // Highlighting the targetWay in Selecting state 239 // Non-native highlighting is used, because sometimes highlighted 240 // segments are covered with others, which is bad. 241 BasicStroke stroke = SELECT_TARGET_WAY_STROKE.get(); 242 g.setStroke(stroke); 243 244 List<Node> nodes = targetWay.getNodes(); 245 246 g.draw(new MapViewPath(mv).append(nodes, false).computeClippedLine(stroke)); 247 248 } else if (state == State.IMPROVING) { 249 // Drawing preview lines and highlighting the node 250 // that is going to be moved. 251 // Non-native highlighting is used here as well. 252 253 // Finding endpoints 254 Node p1 = null; 255 Node p2 = null; 256 if (ctrl && candidateSegment != null) { 257 g.setStroke(ADD_NODE_STROKE.get()); 258 try { 259 p1 = candidateSegment.getFirstNode(); 260 p2 = candidateSegment.getSecondNode(); 261 } catch (ArrayIndexOutOfBoundsException e) { 262 Logging.error(e); 263 } 264 } else if (!alt && !ctrl && candidateNode != null) { 265 g.setStroke(MOVE_NODE_STROKE.get()); 266 List<Pair<Node, Node>> wpps = targetWay.getNodePairs(false); 267 for (Pair<Node, Node> wpp : wpps) { 268 if (wpp.a == candidateNode) { 269 p1 = wpp.b; 270 } 271 if (wpp.b == candidateNode) { 272 p2 = wpp.a; 273 } 274 if (p1 != null && p2 != null) { 275 break; 276 } 277 } 278 } else if (alt && !ctrl && candidateNode != null) { 279 g.setStroke(DELETE_NODE_STROKE.get()); 280 List<Node> nodes = targetWay.getNodes(); 281 int index = nodes.indexOf(candidateNode); 282 283 // Only draw line if node is not first and/or last 284 if (index > 0 && index < (nodes.size() - 1)) { 285 p1 = nodes.get(index - 1); 286 p2 = nodes.get(index + 1); 287 } else if (targetWay.isClosed()) { 288 p1 = targetWay.getNode(1); 289 p2 = targetWay.getNode(nodes.size() - 2); 290 } 291 // TODO: indicate what part that will be deleted? (for end nodes) 292 } 293 294 295 // Drawing preview lines 296 MapViewPath b = new MapViewPath(mv); 297 if (alt && !ctrl) { 298 // In delete mode 299 if (p1 != null && p2 != null) { 300 b.moveTo(p1); 301 b.lineTo(p2); 302 } 303 } else { 304 // In add or move mode 305 if (p1 != null) { 306 b.moveTo(mousePos.x, mousePos.y); 307 b.lineTo(p1); 308 } 309 if (p2 != null) { 310 b.moveTo(mousePos.x, mousePos.y); 311 b.lineTo(p2); 312 } 313 } 314 g.draw(b.computeClippedLine(g.getStroke())); 315 316 // Highlighting candidateNode 317 if (candidateNode != null) { 318 p1 = candidateNode; 319 g.fill(new MapViewPath(mv).shapeAround(p1, SymbolShape.SQUARE, DOT_SIZE.get())); 320 } 321 322 if (!alt && !ctrl && candidateNode != null) { 323 b.reset(); 324 drawIntersectingWayHelperLines(mv, b); 325 g.setStroke(MOVE_NODE_INTERSECTING_STROKE.get()); 326 g.draw(b.computeClippedLine(g.getStroke())); 327 } 328 329 } 330 } 331 332 protected void drawIntersectingWayHelperLines(MapView mv, MapViewPath b) { 333 for (final OsmPrimitive referrer : candidateNode.getReferrers()) { 334 if (!(referrer instanceof Way) || targetWay.equals(referrer)) { 335 continue; 336 } 337 final List<Node> nodes = ((Way) referrer).getNodes(); 338 for (int i = 0; i < nodes.size(); i++) { 339 if (!candidateNode.equals(nodes.get(i))) { 340 continue; 341 } 342 if (i > 0) { 343 b.moveTo(mousePos.x, mousePos.y); 344 b.lineTo(nodes.get(i - 1)); 345 } 346 if (i < nodes.size() - 1) { 347 b.moveTo(mousePos.x, mousePos.y); 348 b.lineTo(nodes.get(i + 1)); 349 } 350 } 351 } 352 } 353 354 // ------------------------------------------------------------------------- 355 // Event handlers 356 // ------------------------------------------------------------------------- 357 @Override 358 public void modifiersExChanged(int modifiers) { 359 if (!MainApplication.isDisplayingMapView() || !MainApplication.getMap().mapView.isActiveLayerDrawable()) { 360 return; 361 } 362 updateKeyModifiersEx(modifiers); 363 updateCursorDependentObjectsIfNeeded(); 364 updateCursor(); 365 updateStatusLine(); 366 temporaryLayer.invalidate(); 367 } 368 369 @Override 370 public void selectionChanged(SelectionChangeEvent event) { 371 if (selectionChangedBlocked) { 372 return; 373 } 374 updateStateByCurrentSelection(); 375 } 376 377 @Override 378 public void mouseDragged(MouseEvent e) { 379 dragging = true; 380 mouseMoved(e); 381 } 382 383 @Override 384 public void mouseMoved(MouseEvent e) { 385 if (!isEnabled()) { 386 return; 387 } 388 389 mousePos = e.getPoint(); 390 391 updateKeyModifiers(e); 392 updateCursorDependentObjectsIfNeeded(); 393 updateCursor(); 394 updateStatusLine(); 395 temporaryLayer.invalidate(); 396 } 397 398 @Override 399 public void mouseReleased(MouseEvent e) { 400 dragging = false; 401 if (!isEnabled() || e.getButton() != MouseEvent.BUTTON1) { 402 return; 403 } 404 405 DataSet ds = getLayerManager().getEditDataSet(); 406 updateKeyModifiers(e); 407 mousePos = e.getPoint(); 408 409 if (state == State.SELECTING) { 410 if (targetWay != null) { 411 ds.setSelected(targetWay.getPrimitiveId()); 412 updateStateByCurrentSelection(); 413 } 414 } else if (state == State.IMPROVING) { 415 // Checking if the new coordinate is outside of the world 416 if (mv.getLatLon(mousePos.x, mousePos.y).isOutSideWorld()) { 417 JOptionPane.showMessageDialog(Main.parent, 418 tr("Cannot add a node outside of the world."), 419 tr("Warning"), JOptionPane.WARNING_MESSAGE); 420 return; 421 } 422 423 if (ctrl && !alt && candidateSegment != null) { 424 // Adding a new node to the highlighted segment 425 // Important: If there are other ways containing the same 426 // segment, a node must added to all of that ways. 427 Collection<Command> virtualCmds = new LinkedList<>(); 428 429 // Creating a new node 430 Node virtualNode = new Node(mv.getEastNorth(mousePos.x, 431 mousePos.y)); 432 virtualCmds.add(new AddCommand(ds, virtualNode)); 433 434 // Looking for candidateSegment copies in ways that are 435 // referenced 436 // by candidateSegment nodes 437 List<Way> firstNodeWays = OsmPrimitive.getFilteredList( 438 candidateSegment.getFirstNode().getReferrers(), 439 Way.class); 440 List<Way> secondNodeWays = OsmPrimitive.getFilteredList( 441 candidateSegment.getFirstNode().getReferrers(), 442 Way.class); 443 444 Collection<WaySegment> virtualSegments = new LinkedList<>(); 445 for (Way w : firstNodeWays) { 446 List<Pair<Node, Node>> wpps = w.getNodePairs(true); 447 for (Way w2 : secondNodeWays) { 448 if (!w.equals(w2)) { 449 continue; 450 } 451 // A way is referenced in both nodes. 452 // Checking if there is such segment 453 int i = -1; 454 for (Pair<Node, Node> wpp : wpps) { 455 ++i; 456 boolean ab = wpp.a.equals(candidateSegment.getFirstNode()) 457 && wpp.b.equals(candidateSegment.getSecondNode()); 458 boolean ba = wpp.b.equals(candidateSegment.getFirstNode()) 459 && wpp.a.equals(candidateSegment.getSecondNode()); 460 if (ab || ba) { 461 virtualSegments.add(new WaySegment(w, i)); 462 } 463 } 464 } 465 } 466 467 // Adding the node to all segments found 468 for (WaySegment virtualSegment : virtualSegments) { 469 Way w = virtualSegment.way; 470 Way wnew = new Way(w); 471 wnew.addNode(virtualSegment.lowerIndex + 1, virtualNode); 472 virtualCmds.add(new ChangeCommand(w, wnew)); 473 } 474 475 // Finishing the sequence command 476 String text = trn("Add a new node to way", 477 "Add a new node to {0} ways", 478 virtualSegments.size(), virtualSegments.size()); 479 480 MainApplication.undoRedo.add(new SequenceCommand(text, virtualCmds)); 481 482 } else if (alt && !ctrl && candidateNode != null) { 483 // Deleting the highlighted node 484 485 //check to see if node is in use by more than one object 486 List<OsmPrimitive> referrers = candidateNode.getReferrers(); 487 List<Way> ways = OsmPrimitive.getFilteredList(referrers, Way.class); 488 if (referrers.size() != 1 || ways.size() != 1) { 489 // detach node from way 490 final Way newWay = new Way(targetWay); 491 final List<Node> nodes = newWay.getNodes(); 492 nodes.remove(candidateNode); 493 newWay.setNodes(nodes); 494 if (nodes.size() < 2) { 495 final Command deleteCmd = DeleteCommand.delete(Collections.singleton(targetWay), true); 496 if (deleteCmd != null) { 497 MainApplication.undoRedo.add(deleteCmd); 498 } 499 } else { 500 MainApplication.undoRedo.add(new ChangeCommand(targetWay, newWay)); 501 } 502 } else if (candidateNode.isTagged()) { 503 JOptionPane.showMessageDialog(Main.parent, 504 tr("Cannot delete node that has tags"), 505 tr("Error"), JOptionPane.ERROR_MESSAGE); 506 } else { 507 final Command deleteCmd = DeleteCommand.delete(Collections.singleton(candidateNode), true); 508 if (deleteCmd != null) { 509 MainApplication.undoRedo.add(deleteCmd); 510 } 511 } 512 513 514 } else if (candidateNode != null) { 515 // Moving the highlighted node 516 EastNorth nodeEN = candidateNode.getEastNorth(); 517 EastNorth cursorEN = mv.getEastNorth(mousePos.x, mousePos.y); 518 519 MainApplication.undoRedo.add(new MoveCommand(candidateNode, cursorEN.east() - nodeEN.east(), cursorEN.north() - nodeEN.north())); 520 } 521 } 522 523 mousePos = null; 524 updateCursor(); 525 updateStatusLine(); 526 temporaryLayer.invalidate(); 527 } 528 529 @Override 530 public void mouseExited(MouseEvent e) { 531 if (!isEnabled()) { 532 return; 533 } 534 535 if (!dragging) { 536 mousePos = null; 537 } 538 temporaryLayer.invalidate(); 539 } 540 541 // ------------------------------------------------------------------------- 542 // Custom methods 543 // ------------------------------------------------------------------------- 544 /** 545 * Sets new cursor depending on state, mouse position 546 */ 547 private void updateCursor() { 548 if (!isEnabled()) { 549 mv.setNewCursor(null, this); 550 return; 551 } 552 553 if (state == State.SELECTING) { 554 mv.setNewCursor(targetWay == null ? cursorSelect 555 : cursorSelectHover, this); 556 } else if (state == State.IMPROVING) { 557 if (alt && !ctrl) { 558 mv.setNewCursor(cursorImproveDelete, this); 559 } else if (shift || dragging) { 560 if (ctrl) { 561 mv.setNewCursor(cursorImproveAddLock, this); 562 } else { 563 mv.setNewCursor(cursorImproveLock, this); 564 } 565 } else if (ctrl && !alt) { 566 mv.setNewCursor(cursorImproveAdd, this); 567 } else { 568 mv.setNewCursor(cursorImprove, this); 569 } 570 } 571 } 572 573 /** 574 * Updates these objects under cursor: targetWay, candidateNode, 575 * candidateSegment 576 */ 577 public void updateCursorDependentObjectsIfNeeded() { 578 if (state == State.IMPROVING && (shift || dragging) 579 && !(candidateNode == null && candidateSegment == null)) { 580 return; 581 } 582 583 if (mousePos == null) { 584 candidateNode = null; 585 candidateSegment = null; 586 return; 587 } 588 589 if (state == State.SELECTING) { 590 targetWay = ImproveWayAccuracyHelper.findWay(mv, mousePos); 591 } else if (state == State.IMPROVING) { 592 if (ctrl && !alt) { 593 candidateSegment = ImproveWayAccuracyHelper.findCandidateSegment(mv, 594 targetWay, mousePos); 595 candidateNode = null; 596 } else { 597 candidateNode = ImproveWayAccuracyHelper.findCandidateNode(mv, 598 targetWay, mousePos); 599 candidateSegment = null; 600 } 601 } 602 } 603 604 /** 605 * Switches to Selecting state 606 */ 607 public void startSelecting() { 608 state = State.SELECTING; 609 610 targetWay = null; 611 612 temporaryLayer.invalidate(); 613 updateStatusLine(); 614 } 615 616 /** 617 * Switches to Improving state 618 * 619 * @param targetWay Way that is going to be improved 620 */ 621 public void startImproving(Way targetWay) { 622 state = State.IMPROVING; 623 624 DataSet ds = getLayerManager().getEditDataSet(); 625 Collection<OsmPrimitive> currentSelection = ds.getSelected(); 626 if (currentSelection.size() != 1 627 || !currentSelection.iterator().next().equals(targetWay)) { 628 selectionChangedBlocked = true; 629 ds.clearSelection(); 630 ds.setSelected(targetWay.getPrimitiveId()); 631 selectionChangedBlocked = false; 632 } 633 634 this.targetWay = targetWay; 635 this.candidateNode = null; 636 this.candidateSegment = null; 637 638 temporaryLayer.invalidate(); 639 updateStatusLine(); 640 } 641 642 /** 643 * Updates the state according to the current selection. Goes to Improve 644 * state if a single way or node is selected. Extracts a way by a node in 645 * the second case. 646 */ 647 private void updateStateByCurrentSelection() { 648 final List<Node> nodeList = new ArrayList<>(); 649 final List<Way> wayList = new ArrayList<>(); 650 final DataSet ds = getLayerManager().getEditDataSet(); 651 if (ds != null) { 652 final Collection<OsmPrimitive> sel = ds.getSelected(); 653 654 // Collecting nodes and ways from the selection 655 for (OsmPrimitive p : sel) { 656 if (p instanceof Way) { 657 wayList.add((Way) p); 658 } 659 if (p instanceof Node) { 660 nodeList.add((Node) p); 661 } 662 } 663 664 if (wayList.size() == 1) { 665 // Starting improving the single selected way 666 startImproving(wayList.get(0)); 667 return; 668 } else if (nodeList.size() == 1) { 669 // Starting improving the only way of the single selected node 670 List<OsmPrimitive> r = nodeList.get(0).getReferrers(); 671 if (r.size() == 1 && (r.get(0) instanceof Way)) { 672 startImproving((Way) r.get(0)); 673 return; 674 } 675 } 676 } 677 678 // Starting selecting by default 679 startSelecting(); 680 } 681}