source: josm/trunk/src/org/openstreetmap/josm/actions/mapmode/ImproveWayAccuracyAction.java @ 12718

Last change on this file since 12718 was 12718, checked in by Don-vip, 3 weeks ago

see #13036 - see #15229 - see #15182 - make Commands depends only on a DataSet, not a Layer. This removes a lot of GUI dependencies

  • Property svn:eol-style set to native
File size: 24.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions.mapmode;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.BasicStroke;
9import java.awt.Color;
10import java.awt.Cursor;
11import java.awt.Graphics2D;
12import java.awt.Point;
13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
15import java.util.ArrayList;
16import java.util.Collection;
17import java.util.Collections;
18import java.util.LinkedList;
19import java.util.List;
20
21import javax.swing.JOptionPane;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.command.AddCommand;
25import org.openstreetmap.josm.command.ChangeCommand;
26import org.openstreetmap.josm.command.Command;
27import org.openstreetmap.josm.command.DeleteCommand;
28import org.openstreetmap.josm.command.MoveCommand;
29import org.openstreetmap.josm.command.SequenceCommand;
30import org.openstreetmap.josm.data.Bounds;
31import org.openstreetmap.josm.data.SelectionChangedListener;
32import org.openstreetmap.josm.data.coor.EastNorth;
33import org.openstreetmap.josm.data.osm.DataSet;
34import org.openstreetmap.josm.data.osm.Node;
35import org.openstreetmap.josm.data.osm.OsmPrimitive;
36import org.openstreetmap.josm.data.osm.Way;
37import org.openstreetmap.josm.data.osm.WaySegment;
38import org.openstreetmap.josm.data.preferences.CachingProperty;
39import org.openstreetmap.josm.data.preferences.ColorProperty;
40import org.openstreetmap.josm.data.preferences.IntegerProperty;
41import org.openstreetmap.josm.data.preferences.StrokeProperty;
42import org.openstreetmap.josm.gui.MainApplication;
43import org.openstreetmap.josm.gui.MapFrame;
44import org.openstreetmap.josm.gui.MapView;
45import org.openstreetmap.josm.gui.draw.MapViewPath;
46import org.openstreetmap.josm.gui.draw.SymbolShape;
47import org.openstreetmap.josm.gui.layer.AbstractMapViewPaintable;
48import org.openstreetmap.josm.gui.layer.Layer;
49import org.openstreetmap.josm.gui.layer.OsmDataLayer;
50import org.openstreetmap.josm.gui.util.ModifierExListener;
51import org.openstreetmap.josm.tools.ImageProvider;
52import org.openstreetmap.josm.tools.Pair;
53import org.openstreetmap.josm.tools.Shortcut;
54
55/**
56 * A special map mode that is optimized for improving way geometry.
57 * (by efficiently moving, adding and deleting way-nodes)
58 *
59 * @author Alexander Kachkaev <alexander@kachkaev.ru>, 2011
60 */
61public class ImproveWayAccuracyAction extends MapMode implements
62        SelectionChangedListener, ModifierExListener {
63
64    private static final String CROSSHAIR = "crosshair";
65
66    enum State {
67        SELECTING, IMPROVING
68    }
69
70    private State state;
71
72    private MapView mv;
73
74    private static final long serialVersionUID = 42L;
75
76    private transient Way targetWay;
77    private transient Node candidateNode;
78    private transient WaySegment candidateSegment;
79
80    private Point mousePos;
81    private boolean dragging;
82
83    private final Cursor cursorSelect = ImageProvider.getCursor("normal", "mode");
84    private final Cursor cursorSelectHover = ImageProvider.getCursor("hand", "mode");
85    private final Cursor cursorImprove = ImageProvider.getCursor(CROSSHAIR, null);
86    private final Cursor cursorImproveAdd = ImageProvider.getCursor(CROSSHAIR, "addnode");
87    private final Cursor cursorImproveDelete = ImageProvider.getCursor(CROSSHAIR, "delete_node");
88    private final Cursor cursorImproveAddLock = ImageProvider.getCursor(CROSSHAIR, "add_node_lock");
89    private final Cursor cursorImproveLock = ImageProvider.getCursor(CROSSHAIR, "lock");
90
91    private Color guideColor;
92
93    private static final CachingProperty<BasicStroke> SELECT_TARGET_WAY_STROKE
94            = new StrokeProperty("improvewayaccuracy.stroke.select-target", "2").cached();
95    private static final CachingProperty<BasicStroke> MOVE_NODE_STROKE
96            = new StrokeProperty("improvewayaccuracy.stroke.move-node", "1 6").cached();
97    private static final CachingProperty<BasicStroke> MOVE_NODE_INTERSECTING_STROKE
98            = new StrokeProperty("improvewayaccuracy.stroke.move-node-intersecting", "1 2 6").cached();
99    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        DataSet.addSelectionListener(this);
157
158        map.keyDetector.addModifierExListener(this);
159    }
160
161    @Override
162    protected void readPreferences() {
163        guideColor = new ColorProperty(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        DataSet.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 l instanceof OsmDataLayer;
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                p1 = candidateSegment.getFirstNode();
259                p2 = candidateSegment.getSecondNode();
260            } else if (!alt && !ctrl && candidateNode != null) {
261                g.setStroke(MOVE_NODE_STROKE.get());
262                List<Pair<Node, Node>> wpps = targetWay.getNodePairs(false);
263                for (Pair<Node, Node> wpp : wpps) {
264                    if (wpp.a == candidateNode) {
265                        p1 = wpp.b;
266                    }
267                    if (wpp.b == candidateNode) {
268                        p2 = wpp.a;
269                    }
270                    if (p1 != null && p2 != null) {
271                        break;
272                    }
273                }
274            } else if (alt && !ctrl && candidateNode != null) {
275                g.setStroke(DELETE_NODE_STROKE.get());
276                List<Node> nodes = targetWay.getNodes();
277                int index = nodes.indexOf(candidateNode);
278
279                // Only draw line if node is not first and/or last
280                if (index != 0 && index != (nodes.size() - 1)) {
281                    p1 = nodes.get(index - 1);
282                    p2 = nodes.get(index + 1);
283                } else if (targetWay.isClosed()) {
284                    p1 = targetWay.getNode(1);
285                    p2 = targetWay.getNode(nodes.size() - 2);
286                }
287                // TODO: indicate what part that will be deleted? (for end nodes)
288            }
289
290
291            // Drawing preview lines
292            MapViewPath b = new MapViewPath(mv);
293            if (alt && !ctrl) {
294                // In delete mode
295                if (p1 != null && p2 != null) {
296                    b.moveTo(p1);
297                    b.lineTo(p2);
298                }
299            } else {
300                // In add or move mode
301                if (p1 != null) {
302                    b.moveTo(mousePos.x, mousePos.y);
303                    b.lineTo(p1);
304                }
305                if (p2 != null) {
306                    b.moveTo(mousePos.x, mousePos.y);
307                    b.lineTo(p2);
308                }
309            }
310            g.draw(b.computeClippedLine(g.getStroke()));
311
312            // Highlighting candidateNode
313            if (candidateNode != null) {
314                p1 = candidateNode;
315                g.fill(new MapViewPath(mv).shapeAround(p1, SymbolShape.SQUARE, DOT_SIZE.get()));
316            }
317
318            if (!alt && !ctrl && candidateNode != null) {
319                b.reset();
320                drawIntersectingWayHelperLines(mv, b);
321                g.setStroke(MOVE_NODE_INTERSECTING_STROKE.get());
322                g.draw(b.computeClippedLine(g.getStroke()));
323            }
324
325        }
326    }
327
328    protected void drawIntersectingWayHelperLines(MapView mv, MapViewPath b) {
329        for (final OsmPrimitive referrer : candidateNode.getReferrers()) {
330            if (!(referrer instanceof Way) || targetWay.equals(referrer)) {
331                continue;
332            }
333            final List<Node> nodes = ((Way) referrer).getNodes();
334            for (int i = 0; i < nodes.size(); i++) {
335                if (!candidateNode.equals(nodes.get(i))) {
336                    continue;
337                }
338                if (i > 0) {
339                    b.moveTo(mousePos.x, mousePos.y);
340                    b.lineTo(nodes.get(i - 1));
341                }
342                if (i < nodes.size() - 1) {
343                    b.moveTo(mousePos.x, mousePos.y);
344                    b.lineTo(nodes.get(i + 1));
345                }
346            }
347        }
348    }
349
350    // -------------------------------------------------------------------------
351    // Event handlers
352    // -------------------------------------------------------------------------
353    @Override
354    public void modifiersExChanged(int modifiers) {
355        if (!MainApplication.isDisplayingMapView() || !MainApplication.getMap().mapView.isActiveLayerDrawable()) {
356            return;
357        }
358        updateKeyModifiersEx(modifiers);
359        updateCursorDependentObjectsIfNeeded();
360        updateCursor();
361        updateStatusLine();
362        temporaryLayer.invalidate();
363    }
364
365    @Override
366    public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
367        if (selectionChangedBlocked) {
368            return;
369        }
370        updateStateByCurrentSelection();
371    }
372
373    @Override
374    public void mouseDragged(MouseEvent e) {
375        dragging = true;
376        mouseMoved(e);
377    }
378
379    @Override
380    public void mouseMoved(MouseEvent e) {
381        if (!isEnabled()) {
382            return;
383        }
384
385        mousePos = e.getPoint();
386
387        updateKeyModifiers(e);
388        updateCursorDependentObjectsIfNeeded();
389        updateCursor();
390        updateStatusLine();
391        temporaryLayer.invalidate();
392    }
393
394    @Override
395    public void mouseReleased(MouseEvent e) {
396        dragging = false;
397        if (!isEnabled() || e.getButton() != MouseEvent.BUTTON1) {
398            return;
399        }
400
401        updateKeyModifiers(e);
402        mousePos = e.getPoint();
403
404        if (state == State.SELECTING) {
405            if (targetWay != null) {
406                getLayerManager().getEditDataSet().setSelected(targetWay.getPrimitiveId());
407                updateStateByCurrentSelection();
408            }
409        } else if (state == State.IMPROVING) {
410            // Checking if the new coordinate is outside of the world
411            if (mv.getLatLon(mousePos.x, mousePos.y).isOutSideWorld()) {
412                JOptionPane.showMessageDialog(Main.parent,
413                        tr("Cannot add a node outside of the world."),
414                        tr("Warning"), JOptionPane.WARNING_MESSAGE);
415                return;
416            }
417
418            if (ctrl && !alt && candidateSegment != null) {
419                // Adding a new node to the highlighted segment
420                // Important: If there are other ways containing the same
421                // segment, a node must added to all of that ways.
422                Collection<Command> virtualCmds = new LinkedList<>();
423
424                // Creating a new node
425                Node virtualNode = new Node(mv.getEastNorth(mousePos.x,
426                        mousePos.y));
427                virtualCmds.add(new AddCommand(virtualNode));
428
429                // Looking for candidateSegment copies in ways that are
430                // referenced
431                // by candidateSegment nodes
432                List<Way> firstNodeWays = OsmPrimitive.getFilteredList(
433                        candidateSegment.getFirstNode().getReferrers(),
434                        Way.class);
435                List<Way> secondNodeWays = OsmPrimitive.getFilteredList(
436                        candidateSegment.getFirstNode().getReferrers(),
437                        Way.class);
438
439                Collection<WaySegment> virtualSegments = new LinkedList<>();
440                for (Way w : firstNodeWays) {
441                    List<Pair<Node, Node>> wpps = w.getNodePairs(true);
442                    for (Way w2 : secondNodeWays) {
443                        if (!w.equals(w2)) {
444                            continue;
445                        }
446                        // A way is referenced in both nodes.
447                        // Checking if there is such segment
448                        int i = -1;
449                        for (Pair<Node, Node> wpp : wpps) {
450                            ++i;
451                            boolean ab = wpp.a.equals(candidateSegment.getFirstNode())
452                                    && wpp.b.equals(candidateSegment.getSecondNode());
453                            boolean ba = wpp.b.equals(candidateSegment.getFirstNode())
454                                    && wpp.a.equals(candidateSegment.getSecondNode());
455                            if (ab || ba) {
456                                virtualSegments.add(new WaySegment(w, i));
457                            }
458                        }
459                    }
460                }
461
462                // Adding the node to all segments found
463                for (WaySegment virtualSegment : virtualSegments) {
464                    Way w = virtualSegment.way;
465                    Way wnew = new Way(w);
466                    wnew.addNode(virtualSegment.lowerIndex + 1, virtualNode);
467                    virtualCmds.add(new ChangeCommand(w, wnew));
468                }
469
470                // Finishing the sequence command
471                String text = trn("Add a new node to way",
472                        "Add a new node to {0} ways",
473                        virtualSegments.size(), virtualSegments.size());
474
475                MainApplication.undoRedo.add(new SequenceCommand(text, virtualCmds));
476
477            } else if (alt && !ctrl && candidateNode != null) {
478                // Deleting the highlighted node
479
480                //check to see if node is in use by more than one object
481                List<OsmPrimitive> referrers = candidateNode.getReferrers();
482                List<Way> ways = OsmPrimitive.getFilteredList(referrers, Way.class);
483                if (referrers.size() != 1 || ways.size() != 1) {
484                    // detach node from way
485                    final Way newWay = new Way(targetWay);
486                    final List<Node> nodes = newWay.getNodes();
487                    nodes.remove(candidateNode);
488                    newWay.setNodes(nodes);
489                    if (nodes.size() < 2) {
490                        final Command deleteCmd = DeleteCommand.delete(Collections.singleton(targetWay), true);
491                        if (deleteCmd != null) {
492                            MainApplication.undoRedo.add(deleteCmd);
493                        }
494                    } else {
495                        MainApplication.undoRedo.add(new ChangeCommand(targetWay, newWay));
496                    }
497                } else if (candidateNode.isTagged()) {
498                    JOptionPane.showMessageDialog(Main.parent,
499                            tr("Cannot delete node that has tags"),
500                            tr("Error"), JOptionPane.ERROR_MESSAGE);
501                } else {
502                    final Command deleteCmd = DeleteCommand.delete(Collections.singleton(candidateNode), true);
503                    if (deleteCmd != null) {
504                        MainApplication.undoRedo.add(deleteCmd);
505                    }
506                }
507
508
509            } else if (candidateNode != null) {
510                // Moving the highlighted node
511                EastNorth nodeEN = candidateNode.getEastNorth();
512                EastNorth cursorEN = mv.getEastNorth(mousePos.x, mousePos.y);
513
514                MainApplication.undoRedo.add(new MoveCommand(candidateNode, cursorEN.east() - nodeEN.east(), cursorEN.north() - nodeEN.north()));
515            }
516        }
517
518        mousePos = null;
519        updateCursor();
520        updateStatusLine();
521        temporaryLayer.invalidate();
522    }
523
524    @Override
525    public void mouseExited(MouseEvent e) {
526        if (!isEnabled()) {
527            return;
528        }
529
530        if (!dragging) {
531            mousePos = null;
532        }
533        temporaryLayer.invalidate();
534    }
535
536    // -------------------------------------------------------------------------
537    // Custom methods
538    // -------------------------------------------------------------------------
539    /**
540     * Sets new cursor depending on state, mouse position
541     */
542    private void updateCursor() {
543        if (!isEnabled()) {
544            mv.setNewCursor(null, this);
545            return;
546        }
547
548        if (state == State.SELECTING) {
549            mv.setNewCursor(targetWay == null ? cursorSelect
550                    : cursorSelectHover, this);
551        } else if (state == State.IMPROVING) {
552            if (alt && !ctrl) {
553                mv.setNewCursor(cursorImproveDelete, this);
554            } else if (shift || dragging) {
555                if (ctrl) {
556                    mv.setNewCursor(cursorImproveAddLock, this);
557                } else {
558                    mv.setNewCursor(cursorImproveLock, this);
559                }
560            } else if (ctrl && !alt) {
561                mv.setNewCursor(cursorImproveAdd, this);
562            } else {
563                mv.setNewCursor(cursorImprove, this);
564            }
565        }
566    }
567
568    /**
569     * Updates these objects under cursor: targetWay, candidateNode,
570     * candidateSegment
571     */
572    public void updateCursorDependentObjectsIfNeeded() {
573        if (state == State.IMPROVING && (shift || dragging)
574                && !(candidateNode == null && candidateSegment == null)) {
575            return;
576        }
577
578        if (mousePos == null) {
579            candidateNode = null;
580            candidateSegment = null;
581            return;
582        }
583
584        if (state == State.SELECTING) {
585            targetWay = ImproveWayAccuracyHelper.findWay(mv, mousePos);
586        } else if (state == State.IMPROVING) {
587            if (ctrl && !alt) {
588                candidateSegment = ImproveWayAccuracyHelper.findCandidateSegment(mv,
589                        targetWay, mousePos);
590                candidateNode = null;
591            } else {
592                candidateNode = ImproveWayAccuracyHelper.findCandidateNode(mv,
593                        targetWay, mousePos);
594                candidateSegment = null;
595            }
596        }
597    }
598
599    /**
600     * Switches to Selecting state
601     */
602    public void startSelecting() {
603        state = State.SELECTING;
604
605        targetWay = null;
606
607        temporaryLayer.invalidate();
608        updateStatusLine();
609    }
610
611    /**
612     * Switches to Improving state
613     *
614     * @param targetWay Way that is going to be improved
615     */
616    public void startImproving(Way targetWay) {
617        state = State.IMPROVING;
618
619        DataSet ds = getLayerManager().getEditDataSet();
620        Collection<OsmPrimitive> currentSelection = ds.getSelected();
621        if (currentSelection.size() != 1
622                || !currentSelection.iterator().next().equals(targetWay)) {
623            selectionChangedBlocked = true;
624            ds.clearSelection();
625            ds.setSelected(targetWay.getPrimitiveId());
626            selectionChangedBlocked = false;
627        }
628
629        this.targetWay = targetWay;
630        this.candidateNode = null;
631        this.candidateSegment = null;
632
633        temporaryLayer.invalidate();
634        updateStatusLine();
635    }
636
637    /**
638     * Updates the state according to the current selection. Goes to Improve
639     * state if a single way or node is selected. Extracts a way by a node in
640     * the second case.
641     *
642     */
643    private void updateStateByCurrentSelection() {
644        final List<Node> nodeList = new ArrayList<>();
645        final List<Way> wayList = new ArrayList<>();
646        final Collection<OsmPrimitive> sel = getLayerManager().getEditDataSet().getSelected();
647
648        // Collecting nodes and ways from the selection
649        for (OsmPrimitive p : sel) {
650            if (p instanceof Way) {
651                wayList.add((Way) p);
652            }
653            if (p instanceof Node) {
654                nodeList.add((Node) p);
655            }
656        }
657
658        if (wayList.size() == 1) {
659            // Starting improving the single selected way
660            startImproving(wayList.get(0));
661            return;
662        } else if (nodeList.size() == 1) {
663            // Starting improving the only way of the single selected node
664            List<OsmPrimitive> r = nodeList.get(0).getReferrers();
665            if (r.size() == 1 && (r.get(0) instanceof Way)) {
666                startImproving((Way) r.get(0));
667                return;
668            }
669        }
670
671        // Starting selecting by default
672        startSelecting();
673    }
674}
Note: See TracBrowser for help on using the repository browser.